前言
异步FIFO是面试中常提的问题
正文
一、什么是异步FIFO
1.1 什么是FIFO
FIFO可以被看做成一个水池:
- 只有写通道打开,表示往水池加水
- 只有读通道打开,表示放水
- 写速度>读速度,可能会写满
- 写速度<读速度,可能会读空
那如何理解先入先出呢?
同样用水池作比喻,我们只考虑理想情况下,先放到水池里的水在池子最下面,那么也先被放出水池
1.2 异步FIFO
同步FIFO:读、写时钟域相同
异步FIFO:读、写时钟域不同,读、写的位宽也可以不同
1.3 异步FIFO的重要概念
FIFO的作用:缓存数据
FIFO的原则:满不写、空不读(否则会造成数据丢失、读取无效数据)
FIFO的关键:设计产生full满信号、empty空信号
二、如何建立FIFO
2.1 FIFO的核心:双端口RAM
双端口:输入端口和输出端口独立,因此可以同时进行读、写;在RAM中,使能信号、数据、数据地址这三个数据是对应的,
2.1.1 SDPRAM:Simple Dual Port RAM
首先明确该模块中可以将读、写端的信号分为:
- 时钟
- 使能信号
- 数据
- 地址
- 读FIFO独有的的复位信号
其次,设定好RAM的宽度和深度
数据宽度:8bit,RAM中存储的数据位宽
地址深度:16bit,RAM中能存多少个位宽为8bit的数据
地址宽度:4bit(2^4=16,使用4位二进制数就能表示16个地址)
写法:16×8
该FIFO能存放:16个8bit的数据
双端口RAM设计文件
实现RAM的读、写操作
module SDPRAM_ly (
input w_en,
input w_clk,
input [7:0] w_data,
input [3:0] w_addr,
input r_en,
input r_clk,
input r_rst_n,
input [3:0] r_addr,
output reg [7:0] r_data
);
// 首先声明一个内存用来放数据和地址
reg [7:0] SAVE [15:0];
// 往SAVE写数据
always @(posedge w_clk) begin
if(w_en)
SAVE[w_addr] <= w_data;
end
// 从SAVE里读数据
always @(posedge r_clk or negedge r_rst_n) begin
if(!r_rst_n)
r_data <= 16'd0;
else if(r_en)
r_data <= SAVE[r_addr];
else
r_data <= r_data;
end
endmodule
2.2 RAM外围模块的核心:设计写满、读空信号
这部分的重点是处理跨时钟域的问题:
单比特:
2.2.1二进制地址自增
在有了双端口RAM后,我们要设计外围的模块去控制RAM正确进行读写操作,下面的核心是如何设计出写满和读空信号
地址自增条件:使能有效+可写/可读
reg [4:0] w_bin;// 对地址扩充一位
reg [4:0] r_bin;
// =========================== 写 ===============================
// 写地址扩充一位后,地址自加1
always @(posedge w_clk or negedge w_rst_n) begin
if(!w_rst_n)
w_bin <= 5'd0;
else if(w_en && !w_full) // && 逻辑与,得到一位;&按位与,保持相与元素位宽
w_bin <= w_bin + 5'd1;
else
w_bin <= w_bin;
end
// =========================== 读 ===============================
always @(posedge r_clk or negedge r_rst_n) begin
if(!r_rst_n)
r_bin <= 5'd0;
else if(r_en && !r_empty) // && 逻辑与,得到一位;&按位与,保持相与元素位宽
r_bin <= r_bin + 5'd1;
else
r_bin <= r_bin;
end
2.2.2 二进制转格雷码:减小跨时钟域下发生亚稳态的概率
这样做的目的:当信号发生变化时,二进制的变化位数远远多于格雷码,因此发生亚稳态的几率就更大,而格雷码只有一位变化
转化方法:
二进制最高位不变,直接复制到格雷码最高位,其余位两两之间异或
二进制转格雷码本身是组合逻辑,但是为了避免产生竞争冒险,于是将组合逻辑的输出打一拍
如果不打拍,可以参照这里的写法:异步FIFO的Verilg实现方法-孤独的单刀
//地址指针从二进制转换成格雷码
assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1);
assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);
打一拍的写法:
// 二进制转格雷码
// =========================== 写 ===============================
// 方法:最高位不变,按位异或
always @(posedge w_clk or negedge w_rst_n) begin
if(!w_rst_n)
w_gay <= 5'd0;
else
w_gay <= {w_bin[4],(w_bin[4:1]^w_bin[3:0])};
end
// =========================== 读 ===============================
// 二进制转格雷码
always @(posedge r_clk or negedge r_rst_n) begin
if(!r_rst_n)
r_gay <= 5'd0;
else
r_gay <= {r_bin[4],(r_bin[4:1]^r_bin[3:0])};
end
2.2.3 将格雷码打2拍:多比特信号的跨时钟域
先将二进制编码为格雷码,然后对格雷码打2拍(同步器)
,将格雷码同步到另一个时钟域下
// =========================== 写 ===============================
// 将读部分得到的格雷码打2拍后,同步到写时钟下
always @(posedge w_clk or negedge w_rst_n) begin
if(!w_rst_n)
r_gay_dff1 <= 5'd0;
r_gay_dff2 <= 5'd0;
else
r_gay_dff1 <= r_gay;
r_gay_dff2 <= r_gay_dff1;
end
// =========================== 读 ===============================
// 将写部分得到的格雷码打2拍后,同步到读时钟下
always @(posedge r_clk or negedge r_rst_n) begin
if(!r_rst_n)
w_gay_dff1 <= 5'd0;
w_gay_dff2 <= 5'd0;
else
w_gay_dff1 <= w_gay;
w_gay_dff2 <= w_gay_dff1;
end
2.2.4 将打2拍后的格雷码解码为二进制:组合逻辑
// =========================== 写 ===============================
// 将同步过来的读格雷码解码为读二进制,方便和写二进制地址比较
always @(*) begin
for(i=4;i>=0;i=i-1)begin
if(i==4)
r_gay_2_bin[i] <= r_gay_dff2[i];
else
r_gay_2_bin[i] <= r_gay_dff2[i] ^ r_gay_2_bin[i+1]
end
end
// =========================== 读 ===============================
// 格雷码解码为二进制
integer j;
always @(*) begin
for(j=4;j>=0;j=j-1)begin
if(j==4)
w_gay_2_bin[j] <= w_gay_dff2[j];
else
w_gay_2_bin[j] <= w_gay_dff2[j] ^ w_gay_2_bin[j+1]
end
end
2.2.5 得到写满、读空信号:组合逻辑
// =========================== 写 ===============================
// 方法一:写满信号:最高位不同,其余位相同——用格雷码
assign w_full = ((w_bin[4] != r_gay_dff2[4]) && (w_bin[3] != r_gay_dff2[3]) && (w_bin[2:0] == r_gay_dff2[2:0]))?1'b1:1'b0;
// 方法二:写满信号:最高位、次高位不同,其余位相同——用格雷码解码后的二进制
assign w_full = ((w_bin[4] != r_gay_2_bin[4]) && (w_bin[3:0] != r_gay_2_bin[3:0]))?1'b1:1'b0;
// =========================== 读 ===============================
// 读空信号:所有位相同
assign r_empty = (r_bin == w_gay_2_bin)?1'b1:1'b0;
2.2.6 汇总
module top_FIFO_ly (
//写
input w_en,
input w_clk,
input w_rst_n,
input [7:0] w_data,
output w_full,
//读
input r_en,
input r_clk,
input r_rst_n,
output r_empty,
output [7:0] r_data
);
reg [4:0] w_bin;// 对地址扩充一位
reg [4:0] r_bin;
reg [4:0] w_gay;
reg [4:0] r_gay;
reg [4:0] r_gay_dff1;
reg [4:0] r_gay_dff2;
reg [4:0] w_gay_dff1;
reg [4:0] w_gay_dff2;
reg [4:0] r_gay_2_bin;
reg [4:0] w_gay_2_bin;
// =========================== 写 ===============================
// 写地址扩充一位后,地址自加1
always @(posedge w_clk or negedge w_rst_n) begin
if(!w_rst_n)
w_bin <= 5'd0;
else if(w_en && !w_full) // && 逻辑与,得到一位;&按位与,保持相与元素位宽
w_bin <= w_bin + 4'd1;
else
w_bin <= w_bin;
end
// 二进制转格雷码
// 方法:最高位不变,按位异或
always @(posedge w_clk or negedge w_rst_n) begin
if(!w_rst_n)
w_gay <= 5'd0;
else
w_gay <= {w_bin[4],(w_bin[4:1]^w_bin[3:0])};
end
// 将读部分得到的格雷码打2拍后,同步到写时钟下
always @(posedge w_clk or negedge w_rst_n) begin
if(!w_rst_n)begin
r_gay_dff1 <= 5'd0;
r_gay_dff2 <= 5'd0;
end
else begin
r_gay_dff1 <= r_gay;
r_gay_dff2 <= r_gay_dff1;
end
end
integer i;
// 将同步过来的读格雷码解码为读二进制,方便和写二进制地址比较
always @(*) begin
for(i=4;i>=0;i=i-1)begin
if(i==4)
r_gay_2_bin[i] <= r_gay_dff2[i];
else
r_gay_2_bin[i] <= r_gay_dff2[i] ^ r_gay_2_bin[i+1];
end
end
// 方法一:写满信号:最高位不同,其余位相同——用格雷码
//assign w_full = ((w_bin[4] != r_gay_dff2[4]) && (w_bin[3] != r_gay_dff2[3]) && (w_bin[2:0] == r_gay_dff2[2:0]))?1'b1:1'b0;
// 方法二:写满信号:最高位、次高位不同,其余位相同——用格雷码解码后的二进制
assign w_full = ((w_bin[4] != r_gay_2_bin[4]) && (w_bin[3:0] == r_gay_2_bin[3:0]))?1'b1:1'b0;
// =========================== 读 ===============================
always @(posedge r_clk or negedge r_rst_n) begin
if(!r_rst_n)
r_bin <= 5'd0;
else if(r_en && !r_empty) // && 逻辑与,得到一位;&按位与,保持相与元素位宽
r_bin <= r_bin + 4'd1;
else
r_bin <= r_bin;
end
// 二进制转格雷码
always @(posedge r_clk or negedge r_rst_n) begin
if(!r_rst_n)
r_gay <= 5'd0;
else
r_gay <= {r_bin[4],(r_bin[4:1]^r_bin[3:0])};
end
// 将写部分得到的格雷码打2拍后,同步到读时钟下
always @(posedge r_clk or negedge r_rst_n) begin
if(!r_rst_n)begin
w_gay_dff1 <= 5'd0;
w_gay_dff2 <= 5'd0;
end
else begin
w_gay_dff1 <= w_gay;
w_gay_dff2 <= w_gay_dff1;
end
end
// 格雷码解码为二进制
integer j;
always @(*) begin
for(j=4;j>=0;j=j-1)begin
if(j==4)
w_gay_2_bin[j] <= w_gay_dff2[j];
else
w_gay_2_bin[j] <= w_gay_dff2[j] ^ w_gay_2_bin[j+1];
end
end
// 读空信号:所有位相同
assign r_empty = (r_bin == w_gay_2_bin)?1'b1:1'b0;
SDPRAM_ly SDPRAM_inst(
.w_en(w_en_RAM),
.w_clk(w_clk),
.w_data(w_data),
.w_addr(w_bin[3:0]),
.r_en(r_en_RAM),
.r_clk(r_clk),
.r_rst_n(r_rst_n),
.r_addr(r_bin[3:0]),
.r_data(r_data)
);
assign w_en_RAM = (w_en && (!w_full))?1'b1:1'b0;
assign r_en_RAM = (r_en && (!r_empty))?1'b1:1'b0;
endmodule
三、如何使用FIFO
这部分内容以后遇到了再进行补充