在计算机硬件设计里,不同模块之间时钟频率不一样是常有的事儿。这时候,跨时钟域处理就变得特别重要啦。同步 FIFO(First In First Out)是处理跨时钟域问题的一个好办法。接下来,咱们就详细聊聊这方面的事儿。

一、跨时钟域问题的由来

想象一下,有两个小伙伴,一个做事儿快,一个做事儿慢。快的那个不停地把任务扔给慢的那个,慢的那个根本来不及处理,这可咋办?在计算机里也是这样,不同的模块可能工作在不同的时钟频率下。比如说,一个模块工作在 100MHz 的时钟频率,另一个模块工作在 50MHz 的时钟频率。当 100MHz 时钟频率的模块要把数据传给 50MHz 时钟频率的模块时,就会出现问题。如果直接传数据,50MHz 的模块可能会漏掉数据或者读到错误的数据。这就是跨时钟域问题。

二、同步 FIFO 是啥

同步 FIFO 就像是一个中间仓库。假如有两个工作速度不一样的模块,一个是发送方,一个是接收方。发送方把数据放到 FIFO 这个“仓库”里,接收方从这个“仓库”里取数据。这样一来,发送方和接收方就不用直接打交道啦,它们只和 FIFO 交互就行。FIFO 会按照数据进入的顺序,依次把数据给接收方,也就是先进先出。

三、同步 FIFO 的设计思路

1. 存储单元

FIFO 得有个地方来存数据,就像仓库得有货架一样。我们可以用一个数组来当这个“货架”。下面是用 Verilog 实现的一个简单的存储单元示例:

// Verilog 技术栈
module fifo_storage (
    input wire clk,  // 时钟信号
    input wire wr_en, // 写使能信号
    input wire [7:0] data_in, // 输入数据,这里是 8 位宽
    input wire [4:0] wr_addr, // 写地址,这里用 5 位宽可以表示 32 个地址
    input wire rd_en, // 读使能信号
    output reg [7:0] data_out, // 输出数据,8 位宽
    input wire [4:0] rd_addr // 读地址,5 位宽
);

    reg [7:0] storage [31:0]; // 定义一个 32 个位置的数组,每个位置存 8 位数据

    always @(posedge clk) begin
        if (wr_en) begin
            storage[wr_addr] <= data_in; // 写数据到指定地址
        end
        if (rd_en) begin
            data_out <= storage[rd_addr]; // 从指定地址读数据
        end
    end

endmodule

2. 读写指针

为了知道数据该存到哪里,从哪里取,我们需要读写指针。写指针就像仓库管理员记录货物存放位置的本子,读指针就像记录货物取出位置的本子。下面是读写指针的 Verilog 示例:

// Verilog 技术栈
module fifo_pointer (
    input wire clk,  // 时钟信号
    input wire rst_n, // 复位信号,低电平有效
    input wire wr_en, // 写使能信号
    input wire rd_en, // 读使能信号
    output reg [4:0] wr_addr, // 写地址
    output reg [4:0] rd_addr // 读地址
);

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            wr_addr <= 5'b00000; // 复位时写地址清零
            rd_addr <= 5'b00000; // 复位时读地址清零
        end else begin
            if (wr_en) begin
                wr_addr <= wr_addr + 1; // 写使能时,写地址加 1
            end
            if (rd_en) begin
                rd_addr <= rd_addr + 1; // 读使能时,读地址加 1
            end
        end
    end

endmodule

3. 状态标志

FIFO 还得有一些状态标志,告诉我们这个“仓库”是满了还是空了。当仓库满了,发送方就不能再往里面放东西了;当仓库空了,接收方就不能再从里面拿东西了。下面是状态标志的 Verilog 示例:

// Verilog 技术栈
module fifo_flag (
    input wire [4:0] wr_addr, // 写地址
    input wire [4:0] rd_addr, // 读地址
    output reg full, // 满标志
    output reg empty // 空标志
);

    always @(*) begin
        if (wr_addr == rd_addr + 1) begin
            full <= 1'b1; // 写地址比读地址多 1 时,认为满了
        end else begin
            full <= 1'b0;
        end
        if (wr_addr == rd_addr) begin
            empty <= 1'b1; // 写地址和读地址一样时,认为空了
        end else begin
            empty <= 1'b0;
        end
    end

endmodule

四、同步 FIFO 的实现技巧

1. 亚稳态处理

同步 FIFO 里可能会出现亚稳态的问题。啥是亚稳态呢?就好比一个人在犹豫向左走还是向右走,状态不稳定。在数字电路里,当信号在不同时钟域之间传输时,就可能出现亚稳态。为了避免亚稳态,我们可以用多级触发器来同步信号。下面是一个简单的多级触发器同步代码示例:

// Verilog 技术栈
module metastability_fix (
    input wire clk,  // 时钟信号
    input wire rst_n, // 复位信号,低电平有效
    input wire async_signal, // 异步信号
    output reg sync_signal // 同步后的信号
);

    reg [1:0] sync_reg; // 定义一个 2 位的寄存器用于同步

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            sync_reg <= 2'b00; // 复位时寄存器清零
        end else begin
            sync_reg[0] <= async_signal; // 第一级触发器
            sync_reg[1] <= sync_reg[0]; // 第二级触发器
            sync_signal <= sync_reg[1]; // 输出同步后的信号
        end
    end

endmodule

2. 格雷码的使用

在读写指针的处理上,我们可以用格雷码。为啥呢?因为格雷码每次只有一位发生变化,这样能减少亚稳态的产生。下面是一个二进制码转格雷码的 Verilog 示例:

// Verilog 技术栈
module binary_to_gray (
    input wire [4:0] binary, // 输入二进制码
    output reg [4:0] gray // 输出格雷码
);

    always @(*) begin
        gray = (binary >> 1) ^ binary; // 二进制码转格雷码的公式
    end

endmodule

五、应用场景

1. 不同时钟频率模块间的数据传输

在一个复杂的芯片里,可能有不同功能的模块,它们的时钟频率不一样。比如,一个图像处理模块工作在高频时钟下,一个存储模块工作在低频时钟下。这时候就可以用同步 FIFO 来实现它们之间的数据传输。

2. 异步通信接口

在一些异步通信接口里,发送端和接收端的时钟可能不同步。同步 FIFO 可以作为一个缓冲,保证数据的正确传输。

六、技术优缺点

优点

  • 数据缓冲:能很好地缓冲数据,解决不同时钟频率模块之间的数据传输问题。
  • 可靠性高:通过状态标志和相应的处理技巧,能保证数据的可靠性。
  • 设计相对简单:相比于其他复杂的跨时钟域处理方法,同步 FIFO 的设计比较容易理解和实现。

缺点

  • 占用资源:需要一定的存储单元和逻辑电路,会占用芯片的资源。
  • 延迟:数据在 FIFO 里会有一定的延迟,对于一些对延迟要求很高的系统可能不太适用。

七、注意事项

1. 复位处理

在设计同步 FIFO 时,复位信号很重要。要保证在复位时,读写指针、存储单元和状态标志都能正确复位。

2. 状态标志的准确性

状态标志(满标志和空标志)的准确性直接影响系统的正常运行。要仔细设计状态标志的逻辑,避免误判。

3. 时钟频率差异

要考虑不同时钟频率之间的差异,合理设计 FIFO 的深度和读写速度。

八、文章总结

同步 FIFO 是处理跨时钟域问题的一个有效方法。通过合理的设计存储单元、读写指针和状态标志,以及运用一些实现技巧,如亚稳态处理和格雷码的使用,能实现不同时钟频率模块之间的数据可靠传输。不过,在使用同步 FIFO 时,也要注意复位处理、状态标志的准确性和时钟频率差异等问题。它在不同时钟频率模块间的数据传输和异步通信接口等场景有广泛的应用,但也存在占用资源和有一定延迟等缺点。