异步FIFO设计及测试

       FIFO: First in, First out。代表先进的数据先出 ,后进的数据后出。FIFO按读数据和写数据工作的时钟域是否相同分为同步FIFO和异步FIFO。由于同步FIFO设计较于简单,故本文仅讨论异步FIFO的设计。

一、异步FIFO结构简述

       由图可见,异步FIFO的核心部件就是一个 双端口 RAM ;左右两边的长条矩形是地址控制器,负责控制地址自增、将二进制地址转为格雷码以及产生空满信号;SYN模块为同步器,由两个D触发器构成,负责将写地址同步至读时钟域、将读地址同步至写时钟域。

二、双端口RAM设计

module RAM #(
    parameter DEPTH=256,   //RAM存储深度
    parameter WIDTH_A=8,   //RAM地址总线宽度
    parameter WIDTH_D=16  //RAM数据总线宽度
)
(
    input r_clk,
    input w_clk,
    input rst_n,

    input [WIDTH_A-1:0] w_addr,              //写地址
    input [WIDTH_D-1:0] w_data,              //写数据
    input w_en,                              //写使能

    input [WIDTH_A-1:0] r_addr,              //读地址
    output reg [WIDTH_D-1:0] r_data,         //读数据
    input r_en                               //读使能
);
    reg [WIDTH_D-1:0] mem [DEPTH-1:0];       //16x256存储器


//RAM写数据
    always @ (posedge w_clk or negedge rst_n)begin
        if (!rst_n)
            mem<='{default:0};
        else if (w_en)
            mem[w_addr]<=w_data;
    end


//RAM读数据
    always @ (posedge r_clk or negedge rst_n)begin
        if (!rst_n)
            r_data<=0;
        else if (r_en)
            r_data<=mem[r_addr];
    end

endmodule

三、二进制转格雷码设计

module bin_to_gray #(
    parameter WIDTH_D=8
)
(
    input [WIDTH_D:0] bin_c,              //输入二进制码
    output reg [WIDTH_D:0] gray_c,       //输出格雷码
    input rst_n
 );


//二进制转格雷码
 always @ (*)begin
     if (!rst_n)
         gray_c=0;
     else
        gray_c={bin_c[WIDTH_D],bin_c[WIDTH_D:1]^bin_c[WIDTH_D-1:0]};
//格雷码最高位与二进制码相同,次高位为二进码高位与次高位异或
end

endmodule

四、同步器设计

module syn #(
    parameter WIDTH=8
)
(
    input syn_clk,                              //同步时钟
    input rst_n,
    input [WIDTH:0] data_in,                   //待同步数据

    output reg [WIDTH:0] data_out             //同步后数据
);

reg [WIDTH:0] data1;


//打两拍同步
always @(posedge syn_clk or negedge rst_n) begin
    if (!rst_n)begin
        data_out<=0;
        data1<=0;
    end

    else begin
        data1<=data_in;
        data_out<=data1;
    end
end

endmodule

五、写控制模块设计

module write_part #(
    parameter WIDTH_D =8
)
(
    input w_clk,
    input rst_n,
    input w_en,
    output w_full,

    input [WIDTH_D:0] r_gaddr,            //格雷码读地址同步到写时钟域
    output reg [WIDTH_D:0] w_addr,
    output reg [WIDTH_D:0] w_gaddr
);


//二进制写地址递增 
always @ (posedge w_clk or negedge rst_n) begin
    if (!rst_n)
        w_addr<=0;
    else if (w_en && (!w_full))
        w_addr<=w_addr+1;
end


//二进制读地址转格雷码
wire [WIDTH_D:0] w_gaddr_w;

bin_to_gray #(
        .WIDTH_D(WIDTH_D)
    )
bin_to_gray_inst(
    .bin_c(w_addr),
    .gray_c(w_gaddr_w),                 //写地址中间格雷码值
    .rst_n(rst_n)
);


//跨时钟域之前要求数据从寄存器送出,所以这里要打一拍
always @ (posedge w_clk or negedge rst_n) begin
    if (!rst_n)
        w_gaddr<=0;
    else if (w_en && (!w_full))
        w_gaddr<=w_gaddr_w;           //写地址输出格雷码值
end


//产生写满信号:
//二进制地址下判断:写地址和读地址最高位相反,其余为都相等,也就是写地址比读地址多跑了一圈
//格雷码地址下判断:写地址和读地址最高两位位相反,其余为都相等,
assign w_full=({~w_gaddr_w[WIDTH_D],~w_gaddr_w[WIDTH_D-1],w_gaddr_w[WIDTH_D-2:0]}==r_gaddr)? 1'b1:1'b0;

endmodule

六、读控制模块设计

module read_part #(
    parameter WIDTH_D =8
)
(
    input r_clk,
    input rst_n,
    input r_en,
    output r_empty,

    input [WIDTH_D:0] w_gaddr,             //格雷码写地址同步到读时钟域
    output reg [WIDTH_D:0] r_addr,
    output reg [WIDTH_D:0] r_gaddr
);
   
//二进制读地址递增
always @ (posedge r_clk or negedge rst_n) begin
    if (!rst_n)
        r_addr<=0;
    else if (r_en && (!r_empty))
        r_addr<=r_addr+1;
end

//二进制读地址转格雷码
wire [WIDTH_D:0] r_gaddr_r;

bin_to_gray #(
        .WIDTH_D(WIDTH_D)
    )
bin_to_gray_inst(
    .bin_c(r_addr),
    .gray_c(r_gaddr_r),                  //读地址中间格雷码值
    .rst_n(rst_n)

);

//跨时钟域之前要求数据从寄存器送出,所以这里要打一拍
always @ (posedge r_clk or negedge rst_n) begin
    if (!rst_n)
        r_gaddr<=0;
    else if (r_en && (!r_empty))
        r_gaddr<=r_gaddr_r;              //写地址输出格雷码值
end

//产生读满信号:读地址等于写地址
assign r_empty=(w_gaddr==r_gaddr)? 1'b1:1'b0;

endmodule

七、整体代码

module asyn_fifo #(
    parameter DEPTH=256,                //FIFO存储深度
    parameter WIDTH_A=8,                //RAM地址总线宽度
    parameter WIDTH_D=16                //FIFO数据总线宽度
 ) 
 (
    input w_clk,                        // 写时钟
    input w_en,                         // 写使能
    input rst_n,                        // 复位信号
    input r_clk,                        // 读时钟
    input r_en,                         // 写使能
    output w_full,                      // 写满标志
    output r_empty,                     // 读空标志
  
    input [WIDTH_D-1:0] w_data,         // 写数据
    output[WIDTH_D-1:0] r_data          // 读数据
);
    wire [WIDTH_A:0] w_addr;            // 二进制写地址
    wire [WIDTH_A:0] r_gaddr_syn;       // 同步到写时钟域的格雷码读地址
    wire [WIDTH_A:0] w_gaddr;           // 格雷码写地址
    wire [WIDTH_A:0] r_addr;            // 二进制读地址
    wire [WIDTH_A:0] w_gaddr_syn;       // 同步到读时钟域的格雷码写地址
    wire [WIDTH_A:0] r_gaddr;           // 格雷码读地址

// 写控制模块例化
write_part #(
    .WIDTH_D(WIDTH_A)
)
write_part_isnt(
    .w_clk(w_clk),
    .rst_n(rst_n),
    .w_en(w_en),
    .r_gaddr(r_gaddr_syn),
    .w_full(w_full),
    .w_addr(w_addr),
    .w_gaddr(w_gaddr)
);

// 双端口RAM模块例化
RAM #(
    .DEPTH(DEPTH),
    .WIDTH_A(WIDTH_A),
    .WIDTH_D(WIDTH_D)
)
inst (
    .r_clk(r_clk),
    .w_clk(w_clk),
    .rst_n(rst_n),
    .w_addr(w_addr[WIDTH_A-1:0]),
    .w_data(w_data),
    .w_en(w_en&(!w_full)),
    .r_addr(r_addr[WIDTH_A-1:0]),
    .r_data(r_data),
    .r_en(r_en&(!r_empty))
);

// 写地址同步到读时钟域
syn #(
    .WIDTH(WIDTH_A)
)
syn_w_2_r
(
    .syn_clk(r_clk),                  // 输入时钟为读时钟
    .rst_n(rst_n),
    .data_in(w_gaddr),
    .data_out(w_gaddr_syn)
);

// 读地址同步到写时钟域
syn #(
    .WIDTH(WIDTH_A)
)
syn_r_2_w
(
    .syn_clk(w_clk),                  // 输入时钟为写时钟
    .rst_n(rst_n),
    .data_in(r_gaddr),
    .data_out(r_gaddr_syn)
);

// 读控制模块例化
read_part #(
    .WIDTH_D(WIDTH_A)
)
read_part_isnt(
    .r_clk(r_clk),
    .rst_n(rst_n),
    .r_en(r_en),
    .w_gaddr(w_gaddr_syn),
    .r_empty(r_empty),
    .r_addr(r_addr),
    .r_gaddr(r_gaddr)
);

endmodule

八、测试代码及仿真波形

module asyn_fifo_tb;

logic w_clk;
logic w_en;
logic w_full;
logic [15:0] w_data;

logic r_clk;
logic r_en;
logic r_empty;
logic [15:0] r_data;

logic rst_n;

asyn_fifo #(
    .DEPTH(256),
    .WIDTH_D(16),
    .WIDTH_A(8)
 )
inst (
    .w_clk(w_clk),
    .w_en(w_en),
    .rst_n(rst_n),
    .r_clk(r_clk),
    .r_en(r_en),
    .w_full(w_full),
    .r_empty(r_empty),
    .w_data(w_data),
    .r_data(r_data)
);

always #2 w_clk=~w_clk;
always #8 r_clk=~r_clk;

initial begin
    w_clk=0;
    r_clk=0;
    rst_n=0;
    w_en=0;
    r_en=0;
    w_data=0;
    #30;
    rst_n=1;
    #20;
    w_en=1;
    r_en=1;

    repeat (512) begin
        @(posedge w_clk)
        if (!w_full)
            w_data=w_data+1'b1;
    end
    
        w_en=0;

    repeat (512)
    @(posedge r_clk);

    $finish;
end

//产生波形文件
initial begin
    $dumpfile("test.fsdb");
    $dumpvars(0,asyn_fifo_tb);
end

endmodule

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
验证异步FIFO复位的测试用例需要确保FIFO在复条件下的行为符合预期。下面是一些测试用例的示例: 1. FIFO复位后无数据:在测试用例中,首先向FIFO写入一些数据,然后对FIFO进行复位操作。接下来,尝试从FIFO读取数据,并验证FIFO是否为空,即没有数据可读。这可以确保FIFO在复位后能够正确地清空数据。 2. 复位期间写入操作:设计一个测试用例,在FIFO进行复位期间,尝试写入一些数据。然后,在复位解除后,尝试读取这些写入的数据,并验证它们是否正确。这可以确保FIFO在复位期间能够正确地忽略写入操作。 3. 复位期间读取操作:在测试用例中,首先向FIFO写入一些数据,然后在FIFO进行复位期间尝试读取数据。然后,确保在复位解除后,FIFO能够正确地恢复并继续传递剩余的数据。这可以验证FIFO在复位期间能够正确地忽略读取操作。 4. 复位后读写操作:设计一个测试用例,在FIFO进行复位后,尝试同时进行读取和写入操作。验证FIFO能够正确处理这些操作,并确保数据的一致性和顺序。 5. 多次复位:设计一个测试用例,连续进行多次复位操作,并验证FIFO在每次复位后是否能够正确地清空数据并恢复到初始状态。 6. 复位信号的持续时间:测试FIFO在复位信号持续时间上的行为。验证FIFO能够在复位信号断言和解除断言时正确处理,并在解除复位后重新接收和传输数据。 这些是一些验证异步FIFO复位的示例测试用例。根据具体的设计规范和需求,你可以进一步扩展和定制这些测试用例,以确保对FIFO的复位行为进行全面的验证。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值