同步FIFO学习笔录--

时间:2022-06-05 08:16:23

同步FIFO学习

1、撰写缘由

这几天在初步学习verilog,学习到了同步FIFO,写点东西记录一下,写写心得体会和大家一起交流学习,中间有不对的地方希望大家能多多包涵,欢迎指正,共同进步。学习时主要参考:https://www.cnblogs.com/SYoong/p/6108780.html,感谢大神的分享。本文与参考有些不同,其中我自己认为有些需要改动的地方,若不对,多多指正。其实同步FIFO在实际中应用很少,应用多的还是异步FIFO,不过作为一个新手拿来练习练习感觉是很不错的。

2、什么是FIFO

         简而言之,FIFO就是先进先出的数据缓存器。没有外部读写地址线,顺序的写入与读出,数据地址由内部读写指针自动加1完成,不能像普通存储器那样由地址线读取或者写入某个指定的地址。

3、FIFO有什么作用

         两个不同时钟域间数据传输使用FIFO作为缓冲;允许时钟进行DMA操作,提高数据传输速度;不同宽度的数据接口也可以使用FIFO等。

4、同步FIFO的Verilog实现

此同步FIFO深度为16,位宽位8,分为了4个子模块:(1)写地址产生模块wr_addr_gen;(2)16个8bits寄存器模块;(3)读地址产生模块;(4)标志位写满full和读空empty模块。

鼓励大家写代码时能够分模块写并且加注释,这样使代码更加清晰,可读性与可移植性更高。

同步FIFO学习笔录--

 

 

 

 

 

 

 

 

 

 

 

(1)写地址产生模块wr_addr_gen

 当写使能有效并且FIFO还没有被写满时,写地址在时钟上升沿时顺序加1。

module w_addr_gen(

                input wire clk, //时钟信号

                input wire full,//写满标志

                input wire wr_en,//写使能

                input wire rst_n,//复位信号,低电平有效

                output reg [3:0] wr_addr//写地址

            );

    always @ (posedge clk) begin

        if (!rst_n) begin

            wr_addr <= 4'b0;

            end else if (!full && wr_en == 1'b1) begin        

                    wr_addr <= wr_addr + 1'b1;

                    end

            end

    endmodule

       

 

(2)读地址产生模块rd_addr_gen

当读使能有效并且FIFO还没有被读空时,读地址在时钟上升沿时顺序加1。

module r_addr_gen (

                input wire clk, //时钟

                input wire rst_n,//复位

                input wire rd_en,//读使能

                input wire empty,//读空标志

                output reg [3:0]rd_addr//读地址信号

            );

    always @ (posedge clk) begin

        if (!rst_n) begin

            rd_addr <= 4'b0;

            end

        else if ((rd_en==1'b1) && (empty != 1'b1)) begin

            rd_addr <= rd_addr + 1'b1;

            end

        end

    endmodule

 

(3)存储器RAM模块的实现

module RAM (

            input wire clk,

            input wire wr_en,

            input wire [3:0] wr_addr,//写地址

            input wire [7:0] data_in, //输入数据

            input wire rd_en, //读使能

            input wire [3:0] rd_addr,//读地址

            input wire full,

            input wire empty,

            output reg [7:0] data_out //输出数据

        );

    reg [7:0] fifo [15:0]; //8bits register*16

    always @ (posedge clk) begin  //write

        if ((wr_en == 1'b1) && (full!=1))

            fifo[wr_addr] <= data_in;

//上一行与参考不一致,当写使能有效并且FIFO未写满时,将数据写入FIFO对应地址

//若写使能有效而FIFO已经处于满状态的话,再写入数据就会覆盖原先地址的数据,导致数据丢失

        end

    always @ (posedge clk) begin //read

        if ((rd_en == 1'b1) && (empty!=1))

//上一行与参考不一致,当读使能有效并且FIFO不为空时,将FIFO中对应地址中的数据读出

//若读使能有效而FIFO为空的话,再读出就会导致读出空数据出错

            data_out <= fifo[rd_addr];

        end

    endmodule  

 

 

 

 

(4)标志位产生模块flag_gen的实现

module flag_gen (

                input wire clk,

                input wire rst_n,

                input wire rd_en,

                input wire wr_en,

                output wire empty,//FIFO空标志位

                output wire full//FIFO满标志位

            );

    parameter max=5'b10000;

    reg [4:0] count;//16位的FIFO用5位的寄存器记录被写入的数据个数,见下

    always @ (posedge clk) begin

        if (!rst_n) begin  

            count <= 5'b0;

        end else case({rd_en,wr_en})

                 2'b00: count <= count; //读写均无效,FIFO中被写入数据个数不变

                 2'b01: if (count!=max) count <= count + 1'b1;//当写有效且FIFO未被写满,计数加1

                 2'b10: if (count!=5'b0000) count <= count - 1'b1;//当读有效且FIFO未被读空,计数减1

                 2'b11: count <= count;

                 default: count <= count;

                 endcase

        end

    assign full = (count == max);//当计数等于16时,产生满信号,所以用5位的count

    //若count==4'b1111产生满信号,会导致FIFO中一个存储空间不能被写入数据,仿真可以试一下

    assign empty = (count == 5'b0000);

    endmodule

 

(5)顶层模块syn_fifo_top的实现

用wire将各个模块连接起来即可。

module syn_fifo_top(

                input wire clk,

                input wire rst_n,

                input wire wr_en,

                input wire rd_en,

                input wire [7:0] data_in,

                output wire [7:0] data_out

);

    wire [3:0] wr_addr;

    wire [3:0] rd_addr;

    RAM iRAM(

            .clk(clk),

            .wr_en(wr_en),

            .wr_addr(wr_addr),

            .data_in(data_in),

            .rd_en(rd_en),

            .rd_addr(rd_addr),

            .data_out(data_out),

            .full(full),

            .empty(empty)

    );

    r_addr_gen ir_addr_gen(

                        .clk(clk),

                        .rst_n(rst_n),

                        .rd_en(rd_en),

                        .empty(empty),

                        .rd_addr(rd_addr)

);

    w_addr_gen iw_addr_gen(

                        .clk(clk),

                        .full(full),

                        .wr_en(wr_en),

                        .rst_n(rst_n),

                        .wr_addr(wr_addr)

    );

    flag_gen iflag_gen(

            .clk(clk),

            .rst_n(rst_n),

            .rd_en(rd_en),

            .wr_en(wr_en),

            .empty(empty),

            .full(full)

    );

endmodule

 

 

 

 

(6)仿真testbench的实现

`timescale 1ns/1ps

`define clk_period 20

module syn_fifo_tb();

    reg clk;

    reg rst_n;

    reg wr_en,rd_en;

    reg [7:0] data_in;

    wire [7:0] data_out;

    syn_fifo_top isyn_fifo_top(

                .clk(clk),

                .rst_n(rst_n),

                .wr_en(wr_en),

                .rd_en(rd_en),

                .data_in(data_in),

                .data_out(data_out)

        );

    initial begin

        clk = 1'b0;

        end

    always #(`clk_period/2) clk = ~clk;

    initial begin  

        wr_en = 1'b1;

        rd_en = 1'b0;

        rst_n = 1'b0;

        data_in =8'b0;

        #(`clk_period+1) rst_n = 1'b1;

        repeat(16) begin

            #(`clk_period+1)

            data_in = data_in + 1'b1;

            end

        # (`clk_period*20+1)

            rd_en = 1'b1;

            wr_en =1'b0;

        # (`clk_period*200+1)

            $finish;

        end

    endmodule

 

5、标志位产生模块flag_gen的另一种实现方法

         分别将读/写地址寄存器扩展一位,将最高位设置为状态位,其余低位作为地址位,指针由地址位以及状态位组成。首先把读、写状态位全部复位,如果地址循环了奇数次,则状态位置1,偶数次则又重新复位,应用地址位和状态位的结合实现对空、满标志位的控制。当读写指针的地址位和状态位全部吻合的时候,读写指针经历了相同次数的循环移动,也就是说,FIFO 处于空状态;如果读写指针的地址位相同而状态位相反,写指针比读指针多循环一次,标志FIFO处于满状态。

module flag_gen (

                input wire [4:0] wr_addr,

                input wire [4:0] rd_addr,

                output wire empty,

                output wire full

                );

    assign full = ((wr_addr[4]!=rd_addr[4]) && (wr_addr[3:0]==rd_addr[3:0]));

    assign empty = (wr_addr == rd_addr);

 endmodule

 

         注意深度为16的FIFO,方法1使用4位的地址线即可,方法二需要使用5位的地址线,即需要将每个模块中的[3:0]wr_addr、[3:0]rd_addr替换为[4:0]wr_addr、[4:0]rd_addr,需要注意一下。