- FIFO
FIFO表示先入先出,是一种存储器结构,被广泛应用于芯片设计当中。FIFO由存储单元阵列或队列组成,第一个被写入的数据也是第一个从数列中读出的数据。在芯片设计中,FIFO可以满足下列需求:
- 当输入速率与输出速率不匹配时,作为临时存储单元。例如,CPU可以先将数据写入FIFO,然后继续其他工作,设备可以很方便地从FIFO中读取数据。再如以太网控制器,它将从网络接收来的数据存入FIFO,后端的DMA(直接存储器访问)控制器从FIFO中读取数据,然后写入存储系统。
- 用于不同时钟域之间的同步。实际应用中,数据将不得不从一个时钟域进入另一个时钟域,此时FIFO不仅用作临时数据存储单元,也起到数据同步的作用。
- 输入数据和输出数据路径之间位宽不匹配时,可以用于数据位宽调整电路。
- FIFO操作
一开始(电路复位后),FIFO为空,写指针和读指针都指向同一个位置,该位置通常为零。w_data端口进来的数据被依次写入FIFO内的不同存储位置上。当FIFO被读出时,r_data端口将数据送出。如果我们持续以每次一个字的速度将数据写人FIFO,w_ptr将不断增加,当达到地址范围的最大值(例如是111)时,w_ptr又回到0。将数据写人FIFO时,w_en信号有效,同时数据需要被提供给写人端口。时钟周期结束时,写指针调整为下一个值。
类似地,当我们需要从FIFO中读取数据时,将r_en信号置为有效。在下一个时钟期开始时,总线r_data的数据可用,r_ptr指向下一个数值,当其达到最大值时,返回到0。换句话说,写指针和读指针以环形的方式移动,写指针在前,读指针追随。持续写入时,写指针会按照如下方式变化:000->001->010->011->100->101->110->111-000->001。写操作和读操作也被称为push/pop、put/get或fill/drain操作。
我们也可以同时对FIFO进行写入和读取操作,因为两种操作使用各自的指针、使能信号和数据总线。FIFO的这种操作就像一个水箱,它有一个进水口让水进入水箱,还有一个出口让水流出水箱。在任何时刻,都可以让水进入水箱,同时又可以从水箱取水。我们只需要关心在任一时钟周期,写入和读取的位置不能相同,因为只有当FIFO为满或空时,写入和读指针才可能相同。很明显,我们要确保不要发生两种情形,一是给满的FIFO写入数据(所有位置都有有效数据,没有多余位置);二是从空的FIFO中读取数据(FIFO中没有有效数据)它们分别被称为上溢(overun,写人满的FIFO)和下溢(underrun,从空的FIFO中读取频据)。FIFO将产生fifo_full和fifo_empty信号,用于表示FIFO是满的还是空的。当FIFO为满时禁止继续写人数据;当FIFO为空时,禁止继续读取数据。FIFO的读写时序图如下所示:
- 同步FIFO
在同步FFO中,单一时钟同时用于写入和读取操作。数据流和相关的控制逻辑在同一个时钟域内处理和工作。同步FIFO用于临时存储数据,此时写入和读取操作可以同时发生,也可发生在不同时刻。由于同步FIFO中只使用了一个时钟,其控制逻辑相对于异步FIFO来说简单得多。前面讨论过一些输入和输出端口,现在需要增加一些有用的输出,如fifo_full、fifo_empty、room_available和data_available。从名称可以看出,fifo_full信号表示FIFO为满的状态,
fifo_empty信号表示FIFO为空的状态。这两个信号(在工业上也被广泛地称为标识)是边界条件,用于提醒外部电路不要对满的FIFO写入和对空的FIFO读出。
FIFO还提供其他标识,如almost_full和almost_empty,用于提供关于FIFO再写人多少会满以及再读出多少会空的信息。例如,所设计的FIFO中还剩余2到3个位置时almost_full有效,那么当almost_full有效时,负责写人的外围电路就应该考虑停止写入,因为从决定停止到w_en信号被置为无效可能还需要多个时钟周期。如果写人逻辑等待fifo_full标识有效后才将w_en信号置为无效,就可能太迟了,当前流水线中可能仍旧有一个或两个数据会被写人FIFO从而导致操作不正确。另外,这也取决于具体的外围电路实现方式,总之必须确保fifo_full开始有效时,w_en无效,这样上溢才不会发生。almost_empty信号也采用类似的工作方式用于阻止某个时刻的数据读取,从而避免下溢。相对于almost_full或almost_empty标识,我们有时更愿意使用room_available和data_available信号所提供的准确数据深度信息。写人逻辑使用room_avail信号,读逻辑使用data_avail信号,结合相关外围逻辑,外部电路可以主动决定采取不同操作的时机。
- 代码实现
`timescale 1ns / 1ps module sync_fifo #( parameter WIDTH = 8, //FIFO宽度 parameter DEPTH = 16 //FIFO深度 ) ( input clk, //系统时钟 input rst_n, //系统复位 input r_en, //读使能 input w_en, //写使能 input [7:0] din, //写入数据 output wire empty, //读空信号 output wire full, //写满信号 output reg [7:0] dout ); reg [WIDTH - 1 :0] fifo_r[DEPTH - 1 : 0]; //二维寄存器实现RAM reg [$clog2(DEPTH):0] r_ptr; //读指针,比地址多一位 reg [$clog2(DEPTH):0] w_ptr; //写指针,比地址多一位 // reg [DEPTH - 1 :0] fifo_cnt; //计数器,用于指示fifo中的数据个数 wire [$clog2(DEPTH) - 1 : 0] wr_ptr_true; //真实写地址指针 wire [$clog2(DEPTH) - 1 : 0] rd_ptr_true; //真实读地址指针 wire wr_ptr_msb; //写地址指针地址最高位 wire rd_ptr_msb; //读地址指针地址最高位 assign {wr_ptr_msb,wr_ptr_true} = w_ptr; //将最高位与其他位拼接 assign {rd_ptr_msb,rd_ptr_true} = r_ptr; //将最高位与其他位拼接 //写操作 always @(posedge clk or negedge rst_n) begin if(!rst_n) w_ptr <= 'd0; else if(w_en && !full)begin w_ptr <= w_ptr + 1'b1; //写入8位数据指针加一 fifo_r[wr_ptr_true] <= din; end end //读操作 always @(posedge clk or negedge rst_n) begin if(!rst_n) r_ptr <= 'd0; else if(r_en && !empty)begin r_ptr <= r_ptr + 1'b1; //读指针加一 dout <= fifo_r[rd_ptr_true]; end end /* always @(posedge clk or negedge rst_n) begin if(!rst_n) fifo_cnt <= 'd0; else if(w_en && r_en && !full && !empty) fifo_cnt <= fifo_cnt; else if(w_en && !full && !r_en) fifo_cnt <= fifo_cnt + 1'b1; else if(r_en && !empty && !w_en) fifo_cnt <= fifo_cnt - 1'b1; else fifo_cnt <= fifo_cnt; end */ //assign full = (fifo_cnt == DEPTH - 1); //assign empty = (fifo_cnt == 0); assign empty = ( w_ptr == r_ptr ) ? 1'b1 : 1'b0; assign full = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0; endmodule
`timescale 1ns / 1ps module tb_sync_fifo; // sync_fifo Parameters parameter PERIOD = 10; parameter WIDTH = 8 ; parameter DEPTH = 16; // sync_fifo Inputs reg clk = 0 ; reg rst_n = 0 ; reg r_en = 0 ; reg w_en = 0 ; reg [7:0] din = 0 ; // sync_fifo Outputs wire empty ; wire full ; wire [7:0] dout ; initial begin forever #(PERIOD/2) clk=~clk; end initial begin #(PERIOD*2) rst_n = 1; end sync_fifo #( .WIDTH ( WIDTH ), .DEPTH ( DEPTH )) u_sync_fifo ( .clk ( clk ), .rst_n ( rst_n ), .r_en ( r_en ), .w_en ( w_en ), .din ( din ), .empty ( empty ), .full ( full ), .dout ( dout ) ); initial begin clk = 1'b1; rst_n = 1'b0; din = 8'd0; r_en = 1'b0; w_en = 1'b0; repeat(16)begin @(posedge clk)begin w_en = 1'b1; r_en = 1'b0; din = {$random % 255}; end end repeat(16)begin @(posedge clk)begin r_en = 1'b1; w_en = 1'b0; end end repeat(11)begin @(posedge clk)begin w_en = 1'b1; r_en = 1'b0; din = {$random % 255}; end end repeat(8)begin @(posedge clk)begin r_en = 1'b1; w_en = 1'b0; end end forever begin @(posedge clk)begin r_en = 1'b1; w_en = 1'b1; din = {$random % 64}; end end $stop; end endmodule
在代码编写过程中,博主想利用计数器来控制full和empty信号的拉高和拉低,结果却出现了有一位写入的数据读不出来的情况,思考了很久还是没有发现问题出在哪里,如果有懂得大佬可以在评论区地点一下,最后又是借鉴了刀哥的代码才实现功能,不得不说刀哥YYDS.本文仅用于记录学习,如有侵权,请联系博主