我用CodeBuddy写了一个async fifo
专栏:ExASIC Dec. 17, 2025, 8:29 a.m. 272 阅读
ai写verilog效果咋样?一起来看看

从生成的文件头来看,主要实现了下面的几点功能:

//==============================================================================
// 异步FIFO (Asynchronous FIFO)
// 功能:在不同时钟域之间安全传递数据
// 特性:
//   - 双时钟域设计(写时钟和读时钟独立)
//   - 格雷码指针减少亚稳态
//   - 空满状态检测
//   - 参数化配置
//==============================================================================

首先,来看看模块参数,默认数据位宽8、fifo深度16(地址线位宽为4)。读写分成两组独立的接口,一组是写接口,包括写时钟、写复位、写使能、写数据和满标志(未满才能写)。读的接口类似,分别为读时钟、读复位、读使能、读数据和空标志(非空才能读)。

module async_fifo #(
    parameter DATA_WIDTH = 8,           // 数据位宽
    parameter FIFO_DEPTH = 16,          // FIFO深度(必须是2的幂次方)
    parameter ADDR_WIDTH = 4            // 地址位宽(log2(FIFO_DEPTH))
)(
    // 写时钟域
    input  wire                 wr_clk,     // 写时钟
    input  wire                 wr_rst_n,   // 写时钟域复位(低电平有效)
    input  wire                 wr_en,      // 写使能
    input  wire [DATA_WIDTH-1:0] wr_data,   // 写数据
    output wire                  full,       // FIFO满标志
    
    // 读时钟域
    input  wire                 rd_clk,     // 读时钟
    input  wire                 rd_rst_n,   // 读时钟域复位(低电平有效)
    input  wire                 rd_en,      // 读使能
    output wire [DATA_WIDTH-1:0] rd_data,  // 读数据
    output wire                  empty       // FIFO空标志
);

然后,定义一些内部信号。

    // 内部信号声明
    reg [ADDR_WIDTH:0] wr_ptr_bin;     // 写指针(二进制)
    reg [ADDR_WIDTH:0] wr_ptr_gray;    // 写指针(格雷码)
    reg [ADDR_WIDTH:0] wr_ptr_gray_next;
    
    reg [ADDR_WIDTH:0] rd_ptr_bin;     // 读指针(二进制)
    reg [ADDR_WIDTH:0] rd_ptr_gray;    // 读指针(格雷码)
    reg [ADDR_WIDTH:0] rd_ptr_gray_next;
    
    wire [ADDR_WIDTH:0] wr_ptr_gray_sync1; // 写指针格雷码同步到读时钟域(第一级)
    wire [ADDR_WIDTH:0] wr_ptr_gray_sync2; // 写指针格雷码同步到读时钟域(第二级)
    wire [ADDR_WIDTH:0] rd_ptr_gray_sync1; // 读指针格雷码同步到写时钟域(第一级)
    wire [ADDR_WIDTH:0] rd_ptr_gray_sync2; // 读指针格雷码同步到写时钟域(第二级)
    
    wire full_int;    // 内部满标志
    wire empty_int;   // 内部空标志
    
    // FIFO存储器
    reg [DATA_WIDTH-1:0] mem [0:FIFO_DEPTH-1];
    reg [DATA_WIDTH-1:0] rd_data_reg;


后面是二进制转格雷码和格雷码转二进制的两个函数,纯组合逻辑,写成function方便调用。

    //==========================================================
    // 二进制转格雷码
    //==========================================================
    function [ADDR_WIDTH:0] bin_to_gray;
        input [ADDR_WIDTH:0] binary;
        begin
            bin_to_gray = binary ^ (binary >> 1);
        end
    endfunction
    
    //==========================================================
    // 格雷码转二进制
    //==========================================================
    function [ADDR_WIDTH:0] gray_to_bin;
        input [ADDR_WIDTH:0] gray;
        integer i;
        begin
            gray_to_bin[ADDR_WIDTH] = gray[ADDR_WIDTH];
            for(i = ADDR_WIDTH-1; i >= 0; i = i-1) begin
                gray_to_bin[i] = gray_to_bin[i+1] ^ gray[i];
            end
        end
    endfunction

接下来是写时钟域的写逻辑。二进制的写指针每写一个数据就加1,直到fifo满了。根据二进制写指针,把写的数据存到mem数组里。格雷码的写指针只是二进制写指针的格式转换。同时,把格雷码的读指针(读时钟域)用写时钟打两拍,同步到写时钟域。

    //==========================================================
    // 写时钟域逻辑
    //==========================================================
    
    // 写指针逻辑
    always @(posedge wr_clk or negedge wr_rst_n) begin
        if (!wr_rst_n) begin
            wr_ptr_bin <= {ADDR_WIDTH+1{1'b0}};
            wr_ptr_gray <= {ADDR_WIDTH+1{1'b0}};
        end else if (wr_en && !full_int) begin
            wr_ptr_bin <= wr_ptr_bin + 1'b1;
            wr_ptr_gray <= bin_to_gray(wr_ptr_bin + 1'b1);
        end
    end
    
    // FIFO写操作
    always @(posedge wr_clk) begin
        if (wr_en && !full_int) begin
            mem[wr_ptr_bin[ADDR_WIDTH-1:0]] <= wr_data;
        end
    end
    
    // 同步读指针到写时钟域
    always @(posedge wr_clk or negedge wr_rst_n) begin
        if (!wr_rst_n) begin
            rd_ptr_gray_sync1 <= {ADDR_WIDTH+1{1'b0}};
            rd_ptr_gray_sync2 <= {ADDR_WIDTH+1{1'b0}};
        end else begin
            rd_ptr_gray_sync1 <= rd_ptr_gray;
            rd_ptr_gray_sync2 <= rd_ptr_gray_sync1;
        end
    end

然后,是读时钟域的读逻辑。非空时,把二进制的读指针指向的数据读到rd_data_reg里。同时二进制读指针加1。格雷码写指针也会有读时钟打两拍,作空标志判断用。

    //==========================================================
    // 读时钟域逻辑
    //==========================================================
    
    // 读指针逻辑
    always @(posedge rd_clk or negedge rd_rst_n) begin
        if (!rd_rst_n) begin
            rd_ptr_bin <= {ADDR_WIDTH+1{1'b0}};
            rd_ptr_gray <= {ADDR_WIDTH+1{1'b0}};
        end else if (rd_en && !empty_int) begin
            rd_ptr_bin <= rd_ptr_bin + 1'b1;
            rd_ptr_gray <= bin_to_gray(rd_ptr_bin + 1'b1);
        end
    end
    
    // FIFO读操作
    always @(posedge rd_clk) begin
        if (rd_en && !empty_int) begin
            rd_data_reg <= mem[rd_ptr_bin[ADDR_WIDTH-1:0]];
        end
    end
    
    // 同步写指针到读时钟域
    always @(posedge rd_clk or negedge rd_rst_n) begin
        if (!rd_rst_n) begin
            wr_ptr_gray_sync1 <= {ADDR_WIDTH+1{1'b0}};
            wr_ptr_gray_sync2 <= {ADDR_WIDTH+1{1'b0}};
        end else begin
            wr_ptr_gray_sync1 <= wr_ptr_gray;
            wr_ptr_gray_sync2 <= wr_ptr_gray_sync1;
        end
    end

最后是空满标志判断逻辑,满标志是在写时钟域里生成,而空标志在读时钟域时生成,均是用格雷码同步之后的指针比较。

    //==========================================================
    // 空满状态判断
    //==========================================================
    
    // 满状态判断(写指针和同步来的读指针的最高位和次高位相反,其余位相同)
    assign full_int = (wr_ptr_gray == {~rd_ptr_gray_sync2[ADDR_WIDTH:ADDR_WIDTH-1], 
                                       rd_ptr_gray_sync2[ADDR_WIDTH-2:0]});
    
    // 空状态判断(读指针和同步来的写指针相同)
    assign empty_int = (rd_ptr_gray == wr_ptr_gray_sync2);
    
    //==========================================================
    // 输出信号
    //==========================================================
    assign full = full_int;
    assign empty = empty_int;
    assign rd_data = rd_data_reg;
    
endmodule
感谢阅读,更多文章点击这里:【专栏:ExASIC】
最新20篇 开设专栏