自建同步时钟,数据等宽FIFO IP 方法

 原文网站:自建同步时钟,数据等宽FIFO IP 方法 – 芯片天地

我们在实际的工作中, 可能会使用一些IP 核, 例如 PLL, MMCM, 双端口ram, fifo 等等。 这时,我们会使用xilinx ISE, vivado , Altera Quartus 等IDE 集成软件, 通过创建厂家给出的一些IP 核 来完成我们工作中一些需求。如果xilinx, Altera 等厂家没有提供我们相应的IP 核 , 或者是他们提供的IP核 不能满足用户的特定需求,这时,就需要用户自主创建一些IP 核,来满足 客户工程的需要了。

本文介绍一种fifo IP 核的创建方法,标准的fifo IP 核 都是会被fpga 厂家提供的, 不论是xilinx, altera ,还是其他公司。 但有时我们编写的verilog 代码 不知道会在什么样的fpga 使用, 有可能是xilinx, 也有可能是altera ,或者是其他公司的fpga。因为 verilog 语言是通用的, 所以通过verilog 语言自建的IP 的适用范围会更加宽广,即使是准备生产芯片(asic 等,不再使用fpga 芯片),verilog 也是非常方便被导入到生产环节的(不必一定需要工厂提供相对应的IP了)

这种自建的fifo IP 会在很多verilog 工程中被应用,这样用户 发布的代码 往往不需要修改, 就可以在不同公司的平台上使用。

本文中自建的IP 是一个同步时钟的FIFO, 同时 fifo 写入 数据的宽度 和数据 读出的宽度 相等。

参考代码:

`timescale 1ns / 1ps
//
// Company: Fraser Innovation Inc
// Engineer: WilliamG
// 
// Create Date: 2021/04/22 09:12:51
// Design Name: 
// Module Name: syn_fifo.v
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//

// first word for through: rd ,next clock get a new data
module syn_fifo # 
(
    parameter ADDR_WIDTH = 3,
    parameter DATA_WIDTH = 8
)
(
    input  clk,

    input  wr_en,
    input  [DATA_WIDTH - 1:0] din,
    output full,

    input  rd_en,
    output [DATA_WIDTH - 1:0] dout,
    output empty,

    output [ADDR_WIDTH:0] data_count,
    output valid,

    input  srst
);

localparam FIFO_DEPTH = 2**ADDR_WIDTH;
reg [DATA_WIDTH - 1:0] ram [0: FIFO_DEPTH - 1];

reg [ADDR_WIDTH - 1:0] rd_addr = 0;
assign dout = ram[rd_addr];

reg [ADDR_WIDTH - 1:0] wr_addr = 0;
wire ptr_match = (wr_addr == rd_addr) ? 1'b1 : 1'b0;

reg maybe_full = 0;
assign empty = ptr_match & (!maybe_full);
assign full = ptr_match & maybe_full;

wire wr_vld = (!full) & wr_en;
wire rd_vld = (!empty) & rd_en;

wire [ADDR_WIDTH:0] wr_sub_rd = wr_addr - rd_addr;
wire [ADDR_WIDTH - 1:0] ptr_diff = wr_sub_rd[ADDR_WIDTH - 1:0];
wire carry_bit = maybe_full & ptr_match;

assign data_count = {carry_bit,ptr_diff};


always @(posedge clk or posedge srst)
if (srst) wr_addr <= 0;
else if (wr_vld) wr_addr <= wr_addr + 1;


wire [DATA_WIDTH - 1:0] din_w = din;
always @(posedge clk or posedge srst)
if(srst) ram[0] <= 0;
else if(wr_vld) ram[wr_addr] <= din_w;


always @(posedge clk or posedge srst)
if (srst) rd_addr <= 0;
else if (rd_vld) rd_addr <= rd_addr + 1;


always @(posedge clk or posedge srst)
if (srst) 
    maybe_full <= 0;
else if (wr_vld != rd_vld)
    maybe_full <= wr_vld;


assign valid = rd_en & (!empty);
endmodule

接口介绍:

module syn_fifo # 
(
    parameter ADDR_WIDTH = 3,      // fifo 地址线 宽度 
    parameter DATA_WIDTH = 8       // fifo 数据线 宽度
)
(
// 同步时钟fifo 的输入时钟,由于是同步fifo ,所以写入和读出使用相同的时钟
input  clk,                        
input  wr_en,                      // fifo 写 信号
input  [DATA_WIDTH - 1:0] din,     // fifo 写 数据
output full,                       // fifo 满 标志

input  rd_en,                      // fifo 读 信号
output [DATA_WIDTH - 1:0] dout,    // fifo 读 数据
output empty,                      // fifo 空 标志

output [ADDR_WIDTH:0] data_count,  // fifo 里当前还有多少数据
output valid,                      // 读出数据有效信号,和dout 是 同步输出的

input  srst                        // fifo 复位信号
);

由于使用了参数化设计, 所以这个fifo IP 存储深度是可以被用户配置的。 同时写入和读出的数据宽度, 用户也是可以配置的。

这里的代码讲解都是以 ADDR_WIDTH = 3, DATA_WIDTH = 8 进行讲解的。其他情况,对代码的理解也是相似的。

FIFO 数据结构定义

// FIFO_DEPTH 表示整体fifo 的深度,例如 ADDR_WIDTH = 3 , 整体的FIFO 深度为 8 ,有8 个存储单元
localparam FIFO_DEPTH = 2**ADDR_WIDTH;  

// 定义fifo 的数据结构, 数据宽度为 8 (缺省值),用户可以更改; FIFO 的深度为 8 (0 - 7 )
reg [DATA_WIDTH - 1:0] ram [0: FIFO_DEPTH - 1]; 

reg [ADDR_WIDTH - 1:0] rd_addr = 0;  // FIFO 读数据指针
assign dout = ram[rd_addr];          // 将fifo 里的数据 输出到端口上
reg [ADDR_WIDTH - 1:0] wr_addr = 0;  // FIFO 写数据指针

FIFO 相关的参数定义

// 写地址指针是否可读地址指针相同,当两个地址相同时,可能是当前fifo 为空,也可能是当前fifo 满
wire ptr_match = (wr_addr == rd_addr) ? 1'b1 : 1'b0; 

/* 
表示 最后一次操作: 1 => 写操作, 0 => 读 操作。 如果是没有操作,
或者是读写同时发生, maybe_full 这个值保持之前的值不变。
*/
reg maybe_full = 0;      

// 读写指针相同, 最后一次操作是 读操作 ,整个fifo 为空
assign empty = ptr_match & (!maybe_full);  

// 读写指针相同, 最后一次操作是 写操作 ,整个fifo 为满
assign full = ptr_match & maybe_full;

FIFO 读写 相关的操作

wire wr_vld = (!full) & wr_en;         // 如果fifo 不是满状态, 同时又是写操作, 这时写有效
wire rd_vld = (!empty) & rd_en;        // 如果fifo 不是空状态, 同时又是读操作, 这时读有效

wire [ADDR_WIDTH:0] wr_sub_rd = wr_addr - rd_addr;          // 写地址指针 - 读地址指针

/* 
    写地址指针 - 读地址指针 的二进制 余数
    1)wr_addr  是 写数据地址指针, rd_addr 是读数据指针。 读写指针都是从 0 开始 一直前进 ,
    加到 7 之后 再 + 1 时 ,回到 0。 
    2)读指针只能小于 , 等于 写指针, 不可能大于 写指针。
    3)写指针 - 读指针 的结果 最大 为 8 , 不可能大于 8
    所以 (举例)
       当 读地址 = 3 , 写地址 = 2 时,代表的是 写指针 2 + 8 , 读指针为 3; ptr_diff = 3'b111
       当 读地址 = 4 , 写地址 = 7 时,代表的是 写指针 7 ,     读指针为 4; ptr_diff = 3'b011

       当 读地址 = 0 , 写地址 = 7 时,代表的是 写指针 7 ,     读指针为 0; ptr_diff = 3'b111 
       当 读地址 = 7 , 写地址 = 0 时,代表的是 写指针 0 + 8 , 读指针为 7; ptr_diff = 3'b001

       当 读地址 = 5 , 写地址 = 5 时,代表的是 写指针 5     , 读指针为 5; ptr_diff = 3'b000
*/
wire [ADDR_WIDTH - 1:0] ptr_diff = wr_sub_rd[ADDR_WIDTH - 1:0];

//读写指针相同,最后一次操作是 写操作,整个fifo 为满,carry_bit = 1;读写指针不同,carry_bit = 0
//读写指针相同,最后一次操作是 读操作,整个fifo 为空,carry_bit = 0;读写指针不同,carry_bit = 0
wire carry_bit = maybe_full & ptr_match;  

// 读写指针相同, fifo full: data_count = 4'b1000; fifo empty: data_count = 4'b0000;
// 读写指针不同, data_count = {1'b0, prt_diff[2:0]};
assign data_count = {carry_bit,ptr_diff};

这里,我们会发现,在计算fifo counter 时 ,不论写地址 是否 小于 读地址 ,只要 写地址 – 读 地址 , prt_diff 的结果都是正确的(1 – 7 ) , 只有 0 ,8 这两个值需要看当前fifo 是空 还是满 才能判断。

写地址计数器

always @(posedge clk or posedge srst)
if (srst) wr_addr <= 0;
else if (wr_vld) wr_addr <= wr_addr + 1;

当wr_en 写fifo 信号有效, fifo 又不是为满时, wr_vld 有效; 这时 写地址 + 1

写数据到存储空间

wire [DATA_WIDTH - 1:0] din_w = din;
always @(posedge clk or posedge srst)
if(srst) ram[0] <= 0;
else if(wr_vld) ram[wr_addr] <= din_w;

当wr_en 写fifo 信号有效, fifo 又不是为满时, wr_vld 有效; 这时 写数据到相应的地址空间中。

读地址计数器

always @(posedge clk or posedge srst)
if (srst) rd_addr <= 0;
else if (rd_vld) rd_addr <= rd_addr + 1;

当rd_en 写fifo 信号有效, fifo 又不是为空时, rd_vld 有效; 这时 读地址 + 1

满足读写不同时发生的最后一次操作

always @(posedge clk or posedge srst)
if (srst) 
    maybe_full <= 0;
else if (wr_vld != rd_vld)
    maybe_full <= wr_vld;

从FIFO 读出的数据有效标志

assign valid = rd_en & (!empty);

当rd_en 有效, 同时 fifo 不为 空, 这时输出的数据是有效的。

仿真代码:

`timescale 1ns / 1ps
//
// Company: Fraser Innovation Inc
// Engineer: WilliamG
// 
// Create Date: 03/02/2021 03:51:26 PM
// Design Name: 
// Module Name: tb_sim
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module tb_sim(

);

reg clk = 0;
always clk = #10 ~clk;


integer i = 0;
integer k = 0;
reg end_flag = 0;

localparam ADDR_WIDTH = 3;
localparam DATA_WIDTH = 8;


reg fifo_wr = 0;
reg [DATA_WIDTH - 1:0] fifo_din = 0;
wire full;

reg fifo_rd = 0;
wire [DATA_WIDTH - 1:0] fifo_dout;
wire empty;

wire [ADDR_WIDTH:0] io_count;

reg reset = 1;


initial
begin
    fifo_wr = 0;
    fifo_rd = 0;
    reset = 1;
    #200;
    reset = 0;
    #100;
    @(posedge clk);

    for(i = 0 ; i < 256; i = i+ 1)
    begin
        while(full) begin fifo_wr = 0; @(posedge clk); end
        fifo_wr = 1;
        fifo_din = fifo_din + 1;
        @(posedge clk);

        fifo_wr = 0;
        @(posedge clk);
    end

    fifo_wr = 0; 
    @(posedge clk); 

end

integer j = 0;
wire valid;

initial
begin
    fifo_wr = 0;
    fifo_rd = 0;
    end_flag = 0;
    #200;
    @(posedge clk);
    j = 0;
    while(empty) @(posedge clk);

    while ( k < 128 )
    begin
        while(empty) begin fifo_rd = 0; @(posedge clk); end
        fifo_rd = 1;
        @(posedge clk);

        while(empty) begin fifo_rd = 0; @(posedge clk); end
        fifo_rd = 1;
        @(posedge clk);

        while(empty) begin fifo_rd = 0; @(posedge clk); end
        fifo_rd = 1;
        @(posedge clk);

        fifo_rd = 0;
        @(posedge clk);

        while(empty) begin fifo_rd = 0; @(posedge clk); end
        fifo_rd = 1;
        @(posedge clk);
    end

    fifo_rd = 0;
    for(j = 0 ; j < 17; j = j+ 1)
    begin
        @(posedge clk);
    end

    while( k < 248 )
    begin
        while(empty) begin fifo_rd = 0; @(posedge clk); end
        fifo_rd = 1;
        @(posedge clk);

        while(empty) begin fifo_rd = 0; @(posedge clk); end
        fifo_rd = 1;
        @(posedge clk);

        while(empty) begin fifo_rd = 0; @(posedge clk); end
        fifo_rd = 1;
        @(posedge clk);

        fifo_rd = 0;
        @(posedge clk);

        while(empty) begin fifo_rd = 0; @(posedge clk); end
        fifo_rd = 1;
        @(posedge clk);
    end

    fifo_rd = 0;
    @(posedge clk);
    @(posedge clk);
    @(posedge clk);

    while( k < 256 )
    begin
        while(empty) @(posedge clk);

        fifo_rd = 1;
        @(posedge clk);
        fifo_rd = 0;
        @(posedge clk);
    end

    fifo_rd = 0;
    @(posedge clk);
    end_flag = 1;
end

initial
begin
    k = 0;
    while (1)
    begin
        if(valid) k = k + 1;
        @(posedge clk);
    end
end


syn_fifo # 
(
    .ADDR_WIDTH (ADDR_WIDTH),
    .DATA_WIDTH (DATA_WIDTH)
)
syn_fifo_inst
(
    .clk        (clk),

    .wr_en      (fifo_wr),
    .din        (fifo_din),
    .full       (full),

    .rd_en      (fifo_rd),
    .dout       (fifo_dout),
    .empty      (empty),

    .data_count (io_count),
    .valid      (valid),

    .srst       (reset)
);


end_flag end_flag_inst
(
    .clk      (clk),
    .end_flag (end_flag)

);
//==================================================================
endmodule

仿真波形

reset 过后,fifo counter 为0;fifo_rd 可以连续读, 只要是valid, 输出的fifo_dout 都是有效的。empty 时, valid 为 0, 输出的 fifo_dout 无效。

fifo full 时, io_count = 8; 当 full = 0 时, 又可以继续写fifo 了。

当k = 256 时, 表示 valid 出现过256 次, 这和fifo 写入的 数值个数 一直, 到此, fifo 写 256 个数据, fifo 读 256 个数据 完毕。

总结

到此, 我们的同步fifo 设计完成了。

自建的fifo的优点是不需要 xilinx , altera 或者其他公司的IP, verilog 代码可以快速移植, 甚至生产。甚至可以根据自己的要求,设计fifo 输入,输出格式。

目前这个fifo 的缺点是 只能使用同步时钟(写时钟和读时钟是一致的),写数据宽度 和 读数据的宽度必须一致的。 当然,用户掌握了自建IP 的方法, 也可以解决这些问题的。

附件下载

SYN_FIFO_SIM_2018.2.ZIP

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Vivado中使用同步FIFO IP核的步骤如下: 1. 打开Vivado工程,点击IP Catalog选项卡。 2. 在IP Catalog中,搜索并选择FIFO IP核。 3. 点击"Create"按钮,进入FIFO IP核的配置向导。 4. 在配置向导中,选择FIFO类型为同步FIFO(SCFIFO)。 5. 根据需求,配置FIFO的参数,比如数据宽度、深度等。可以参考中关于FIFO参数的详细说明。 6. 配置FIFO时钟参数,包括输入时钟和输出时钟的频率和相位关系。根据具体设计需求,可以选择不同的时钟域之间的相位关系。 7. 根据需要选择标准模式或者First Word Fall(FWF)模式。标准模式是数据延时一个时钟周期进入或者输出,而FWF模式则是数据直接随时钟同步进入或者输出。具体的选择可以参考中的说明。 8. 完成FIFO IP核的配置后,点击"Finish"按钮生成IP核。 9. 将生成的IP核添加到设计中,并连接输入和输出接口。 10. 编译并实现设计。 通过以上步骤,你可以在Vivado中使用同步FIFO IP核来实现你的设计需求。请注意,具体的配置和连接方式可能会根据你的具体设计需求有所不同,因此请参考Vivado的官方文档或者参考中提供的资料来获取更详细的信息。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [Vivado IPfifo使用指南](https://blog.csdn.net/baidu_25816669/article/details/88941458)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [IP核的使用之FIFO(Vivado)](https://blog.csdn.net/yifantan/article/details/127515689)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值