在芯片设计中,FIFO(先进先出队列)是一个非常经典且重要的模块,主要用来存储数据、跨时钟域同步、打拍等。FIFO内部的数据存储结构可以使用寄存器(ff触发器)或者SRAM来搭建。
根据写时钟和读时钟是否同步,可以将FIFO分为同步FIFO和异步FIFO。根据读写端口数量可以分为单端口FIFO(只有一个写端口和一个读端口)和多端口FIFO(有多个写端口或多个读端口)。根据读数据是否可预取分为可预取FIFO和不可预取FIFO。可预取表示数据在读之前就是有效的(队首数据),可以在读有效当拍直接使用。
本文介绍同步预取FIFO的verilog代码实现。同步FIFO的核心在于准确判断空和满的状态。这里使用一种常用的指针扩展法(也叫高位异或判断法),通过增加一位读写指针位宽,解决了普通地址比较时“读指针等于写指针”既可能是空也可能是满的问题。经评论区指正,将空满判断由时序逻辑改为组合逻辑,避免溢出。
module sync_prefetch_fifo #(parameter DATA_WIDTH = 8, // 数据位宽parameter DEPTH = 16, // FIFO深度parameter ADDR_WIDTH = $clog2(DEPTH) // 地址位宽)(input clk, // 时钟input rst_n, // 异步复位input wr_en, // 写使能input rd_en, // 读使能input [DATA_WIDTH-1:0] wr_data, // 写入数据output [DATA_WIDTH-1:0] rd_data, // 读出数据output reg full, // 满标志output reg empty // 空标志);// 定义FIFO存储阵列reg [DATA_WIDTH-1:0] mem [DEPTH-1:0];// 扩展一位的读写指针(例如深度16需要4位地址,指针设为5位)reg [ADDR_WIDTH:0] wr_ptr;reg [ADDR_WIDTH:0] rd_ptr;// 实际的写/读允许信号(防止在满时写、空时读)wire wr_allow = wr_en & !full;wire rd_allow = rd_en & !empty;// ---------------- 读写指针更新逻辑 ----------------// 写指针:当允许写入时,指针加1always @(posedge clk or negedge rst_n) beginif (!rst_n)wr_ptr <= 'd0;else if (wr_allow)wr_ptr <= wr_ptr + 1'b1;end// 读指针:当允许读取时,指针加1always @(posedge clk or negedge rst_n) beginif (!rst_n)rd_ptr <= 'd0;else if (rd_allow)rd_ptr <= rd_ptr + 1'b1;end// ---------------- 数据存储与读出逻辑 ----------------// 写入数据到RAMinteger i;always @(posedge clk or negedge rst_n) beginif (!rst_n) begin// 复位时清空存储器(可选,视需求而定)for (i = 0; i < DEPTH; i = i + 1)mem[i] <= 'd0;endelse if (wr_allow) begin//写入时使用指针的低ADDR_WIDTH位作为地址mem[wr_ptr[ADDR_WIDTH-1:0]] <= wr_data;endend// 读出数据,预取数据assign rd_data = mem[rd_ptr[ADDR_WIDTH-1:0]];// ---------------- 空满状态判断逻辑 ----------------//always @(posedge clk or negedge rst_n) begin// if (!rst_n) begin// full <= 1'b0;// empty <= 1'b1;// end// else begin// // 满条件:最高位不同,其余位相同assign full = (wr_ptr[ADDR_WIDTH] != rd_ptr[ADDR_WIDTH]) &(wr_ptr[ADDR_WIDTH-1:0] == rd_ptr[ADDR_WIDTH-1:0]);//// // 空条件:所有位完全相同assign empty = (wr_ptr == rd_ptr);// end//endendmodule
FIFO的空满判断除了使用指针扩展法(也叫高位异或判断法),还可以使用计数器来统计FIFO深度的占用,具体如下:
每写入FIFO一次时,计数器加1,每读出一次FIFO时,加速器减1。当计数器等于FIFO深度时,表示FIFO满;当计数器为0时,表示FIFO空。Verilog代码如下:
reg [ADDR_WIDTH:0] count;always @(posedge clk or negedge rst_n) beginif (!rst_n)count <= 'd0;else if (wr_allow & !rd_allow)count <= count + 1'b1;else if (!wr_allow & rd_allow)count <= count - 1'b1;endassign full = (count == DEPTH);assign empty = (count == 0);
阅读全文
174