LOADING

加载过慢请开启缓存 浏览器默认开启

Verilog-FIFO设计

2023/10/2 Verilog

Abstract: Verilog 同步及异步 FIFO  设计

FIFO:First Input First Output

同步 FIFO

解析

(原理很简单所以不写了)

示例代码

题目见:同步FIFO_牛客题霸_牛客网 (nowcoder.com)

`timescale 1ns/1ns
/**********************************RAM************************************/
module dual_port_RAM #(parameter DEPTH = 16,
                       parameter WIDTH = 8)(
     input wclk
    ,input wenc
    ,input [$clog2(DEPTH)-1:0] waddr  //深度对2取对数,得到地址的位宽。
    ,input [WIDTH-1:0] wdata      	//数据写入
    ,input rclk
    ,input renc
    ,input [$clog2(DEPTH)-1:0] raddr  //深度对2取对数,得到地址的位宽。
    ,output reg [WIDTH-1:0] rdata 		//数据输出
);

reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

always @(posedge wclk) begin
    if(wenc)
        RAM_MEM[waddr] <= wdata;
end 

always @(posedge rclk) begin
    if(renc)
        rdata <= RAM_MEM[raddr];
end 

endmodule  

/**********************************SFIFO************************************/
module sfifo#(
    parameter	WIDTH = 8,
    parameter 	DEPTH = 16
)(
    input 					clk		, 
    input 					rst_n	,
    input 					winc	,
    input 			 		rinc	,
    input 		[WIDTH-1:0]	wdata	,

    output reg				wfull	,
    output reg				rempty	,
    output wire [WIDTH-1:0]	rdata
);

    reg [$clog2(DEPTH)-1:0] waddr, raddr;
    reg [$clog2(DEPTH):0] cnt;

    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            waddr <= 'd0;
        end
        else begin
            waddr <= (winc&&(~wfull))? waddr+'d1 : waddr;  // 不满时可写入
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            raddr <= 'd0;
        end
        else begin
            raddr <= (rinc&&(~rempty))? raddr+'d1 : raddr;  // 不空时可读出
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            cnt <= 'd0;
        end
        else begin
            if (winc&&(~wfull)) begin  // 写操作 计数器增
                cnt <= cnt + 'd1;
            end
            else if (rinc&&(~rempty)) begin  // 读操作 计数器减
                cnt <= cnt - 'd1;
            end
            else if (winc&&(~wfull) && rinc&&(~rempty)) begin  // 同时读写 不变
                cnt <= cnt;
            end
            else begin
                cnt <= cnt;
            end
        end
    end

    always @(posedge clk or negedge rst_n) begin
        if (~rst_n) begin
            wfull = 'd0;
            rempty = 'd0;
        end
        else begin
            wfull = (cnt==DEPTH);
            rempty = (cnt=='d0);
        end
    end

    dual_port_RAM #(
        .DEPTH(DEPTH),
        .WIDTH(WIDTH)
    ) u_ram (
        .wclk(clk),
        .wenc(winc&&(~wfull)),
        .waddr(waddr),
        .wdata(wdata),
        .rclk(clk),
        .renc(rinc&&(~rempty)),
        .raddr(raddr),
        .rdata(rdata)
    );

endmodule

异步 FIFO

解析

(其他人写的非常好了)

使用格雷码进行地址比较很有借鉴价值

题解 | #异步FIFO#_牛客博客 (nowcoder.net)

示例代码

题目见:异步FIFO_牛客题霸_牛客网 (nowcoder.com)

`timescale 1ns/1ns

/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
                       parameter WIDTH = 8)(
    input wclk,
    input wenc,
    input [$clog2(DEPTH)-1:0] waddr,  //深度对2取对数,得到地址的位宽。
    input [WIDTH-1:0] wdata,     	//数据写入
    input rclk,
    input renc,
    input [$clog2(DEPTH)-1:0] raddr,  //深度对2取对数,得到地址的位宽。
    output reg [WIDTH-1:0] rdata		//数据输出
);

    reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

    always @(posedge wclk) begin
        if(wenc)
            RAM_MEM[waddr] <= wdata;
    end

    always @(posedge rclk) begin
        if(renc)
            rdata <= RAM_MEM[raddr];
    end

endmodule  

/***************************************AFIFO*****************************************/
module asyn_fifo#(
    parameter	WIDTH = 8,
    parameter 	DEPTH = 16
)(
    input 					wclk	, 
    input 					rclk	,   
    input 					wrstn	,
    input					rrstn	,
    input 					winc	,
    input 			 		rinc	,
    input 		[WIDTH-1:0]	wdata	,

    output wire				wfull	,
    output wire				rempty	,
    output wire [WIDTH-1:0]	rdata
);

    parameter ADDR_WIDTH = $clog2(DEPTH);

// 二进制地址生成
    reg [ADDR_WIDTH:0] waddr_bin, raddr_bin;

    always @(posedge wclk or negedge wrstn) begin
        if (~wrstn) begin
            waddr_bin <= 'b0;
        end
        else begin
            waddr_bin <= (winc&&(~wfull))? waddr_bin+'d1 : waddr_bin;
        end
    end

    always @(posedge rclk or negedge rrstn) begin
        if (~rrstn) begin
            raddr_bin <= 'b0;
        end
        else begin
            raddr_bin <= (rinc&&(~rempty))? raddr_bin+'d1 : raddr_bin;
        end
    end

// 二进制地址转格雷码 减小时钟同步时的错误率
    reg [ADDR_WIDTH:0] waddr_gray, raddr_gray;

    always @(posedge wclk or negedge wrstn) begin
        if (~wrstn) begin
            waddr_gray <= 'b0;
        end
        else begin
            waddr_gray <= waddr_bin^(waddr_bin>>1);
        end
    end

    always @(posedge rclk or negedge rrstn) begin
        if (~rrstn) begin
            raddr_gray <= 'b0;
        end
        else begin
            raddr_gray <= raddr_bin^(raddr_bin>>1);
        end
    end

// 格雷码地址时钟同步 打两拍
    reg [ADDR_WIDTH:0] waddr_gray_sync, waddr_gray_sync_r1;
    reg [ADDR_WIDTH:0] raddr_gray_sync, raddr_gray_sync_r1;

    always @(posedge rclk or negedge rrstn) begin  // 读地址同步到写时钟
        if (~rrstn) begin
            waddr_gray_sync_r1 <= 'b0;
            waddr_gray_sync <= 'b0;
        end
        else begin
            waddr_gray_sync_r1 <= waddr_gray;
            waddr_gray_sync <= waddr_gray_sync_r1;
        end
    end

    always @(posedge wclk or negedge wrstn) begin  // 写地址同步到读时钟
        if (~wrstn) begin
            raddr_gray_sync_r1 <= 'b0;
            raddr_gray_sync <= 'b0;
        end
        else begin
            raddr_gray_sync_r1 <= raddr_gray;
            raddr_gray_sync <= raddr_gray_sync_r1;
        end
    end

// 满空信号逻辑 使用格雷码地址
    assign wfull = (waddr_gray == {~raddr_gray_sync[ADDR_WIDTH:ADDR_WIDTH-1], raddr_gray_sync[ADDR_WIDTH-2:0]});  // 写满信号 写地址超前读地址一圈 此时格雷码两地址的前两位互逆
    assign rempty = (raddr_gray == waddr_gray_sync);  // 读空信号 读地址与写地址相等

// RAM 实例化
    dual_port_RAM #(
        .WIDTH(WIDTH),
        .DEPTH(DEPTH)
    ) u_ram (
        .wclk(wclk),
        .wenc(winc&&(~wfull)),
        .waddr(waddr_bin[ADDR_WIDTH-1:0]),  //深度对2取对数,得到地址的位宽。
        .wdata(wdata),      	//数据写入
        .rclk(rclk),
        .renc(rinc&&(~rempty)),
        .raddr(raddr_bin[ADDR_WIDTH-1:0]),  //深度对2取对数,得到地址的位宽。
        .rdata(rdata) 		//数据输出
    );

endmodule