异步FIFO
描述
请根据题目中给出的双口RAM代码和接口描述,实现异步FIFO,要求FIFO位宽和深度参数化可配置。
电路的接口如下图所示。
双口RAM端口说明:
端口名 | I/O | 描述 |
wclk | input | 写数据时钟 |
wenc | input | 写使能 |
waddr | input | 写地址 |
wdata | input | 输入数据 |
rclk | input | 读数据时钟 |
renc | input | 读使能 |
raddr | input | 读地址 |
rdata | output | 输出数据 |
异步FIFO端口说明:
端口名 | I/O | 描述 |
wclk | input | 写时钟 |
rclk | input | 读时钟 |
wrstn | input | 写时钟域异步复位 |
rrstn | input | 读时钟域异步复位 |
winc | input | 写使能 |
rinc | input | 读使能 |
wdata | input | 写数据 |
wfull | output | 写满信号 |
rempty | output | 读空信号 |
rdata | output | 读数据 |
双口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
输入描述:
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
解题思路
同步FIFO的设计原理及代码
【Verilog学习日常】—牛客网刷题—Verilog企业真题—VL68-CSDN博客
同步FIFO和异步FIFO
主要参考以下博文:
(1)什么是异步FIFO?与同步FIFO有何不同?异步FIFO的设计理念和设计要点是什么?同步FIFO和异步FIFO的应用场景分别是什么? - CSDN文库
同步FIFO和异步FIFO总结[通俗易懂]-腾讯云开发者社区-腾讯云 (tencent.com)
同步FIFO和异步FIFO
同步FIFO和异步FIFO是两种不同的数据传输方式。
同步FIFO是一种基于时钟的数据传输方式,数据的输入和输出都是在时钟边沿进行的。在同步FIFO中,数据的输入和输出操作是同步的,即在每个时钟周期内,输入和输出操作需要在时钟的边沿进行。这种同步方式可以确保数据的稳定性和可靠性,但需要保证输入和输出的时钟频率一致。
异步FIFO是一种不依赖时钟的数据传输方式,数据的输入和输出是根据输入端和输出端的请求来进行的。在异步FIFO中,输入和输出操作是异步的,数据可以在不同的的时间进行输入和输出操作。这种方式相对于同步FIFO来说更加灵活,但需要额外的电路来处理输入和输出之间的时序问题。
同步FIFO和异步FIFO的优点
同步FIFO的优点:
- 同步FIFO在数据读写时使用相同的时钟,因此不需要考虑时钟域的问题,设计和验证相对简单。
- 同步FIFO的读写造作是同步的,可以保证数据的可靠性和一致性。
- 同步FIFO的读写指针可以通过同步逻辑进行控制,可以实现更复杂的读写操作。
异步FIFO的优点:
- 异步FIFO可以在不同的时钟域之间进行数据传输,适用于异步系统或者时钟频率不同的系统。
- 异步FIFO的读写操作是异步的,可以实现更高的并发性和吞吐量。
- 异步FIFO的读写指针可以通过异步逻辑进行控制,可以实现更灵活的读写操作。
FIFO的使用场景
- 数据缓冲——当数据写入过快,并且间隔时间长(突发写入)。那么通过设置一定深度的FIFO,可以起到数据暂存的功能,且使得后续处理流程平滑。
- 时钟域的隔离——主要用于异步FIFO。对于不同时钟域的数据传输,可以通过FIFO进行隔离,避免跨时钟域的数据传输带来的设计和约束上的复杂度。
- 同于不同宽度的数据接口。例如单片机1是8位,DSP是16.
异步FIFO的设计原理
主要参考以下博文:
verilog实现异步FIFO_异步fifo verilog代码-CSDN博客
同步FIFO和异步FIFO总结_synchronization stages-CSDN博客
对于FIFO的设计,最重要的两点如下:
- 读写时钟的移动
- FIFO队列空满检测(对于异步FIFO的空满检测,还涉及到一个跨时钟域问题)
异步FIFO的跨时钟域问题
将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题。因为采用二进制计数器时所有位都有可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码(Gray码)时只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该Gray码同步到另一个时钟域进行对比,作为空满状态的检测(还需添加一个二级同步器)。
下面画出异步FIFO的指针同步电路
对异步FIFO的Verilog代码设计,主要分为以下几个模块:
1.定义读写指针
该步骤与同步FIFO类似,唯一不同的点在于异步FIFO的读写操作中的时钟信号和异步复位信号是不同的;
assign wenc = winc & (~wfull);
assign renc = rinc & (~rempty);
//定义读写指针
parameter POINT_WIDTH = $clog2(DEPTH);
reg [POINT_WIDTH: 0] w_point_b, r_point_b, w_point_g, r_point_g;
//写指针
always @(posedge wclk or negedge wrstn) begin
if (!wrstn) w_point_b <= 'b0;
else begin
if (wenc) w_point_b <= w_point_b + 1'b1; //同步FIFO写法
else w_point_b <= w_point_b;
end
end
//读指针
always @(posedge rclk or negedge rrstn) begin
if (!rrstn) r_point_b <= 'b0;
else begin
if (renc) r_point_b <= r_point_b + 1'b1; //同步FIFO写法
else r_point_b <= r_point_b;
end
end
2.二进制到gray码的转换电路
//二进制码转换为Gray码
Bit_To_Gray BG_W ( .Bit_Code(w_point_b), .clk(wclk), .rst_n(wrstn), .Gray_Code(w_point_g_w));
Bit_To_Gray BG_R ( .Bit_Code(r_point_b), .clk(rclk), .rst_n(rrstn), .Gray_Code(r_point_g_w));
always @(*) begin
w_point_g <= w_point_g_w;
r_point_g <= r_point_g_w;
end
module Bit_To_Gray
(input [4:0] Bit_Code,
input clk,
input rst_n,
output reg[4:0] Gray_Code);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) Gray_Code <= 'd0;
else Gray_Code <= (Bit_Code >> 1) ^ Bit_Code;
end
endmodule
3.同步信号
同步信号为空满检测的前一步;
- 当需要从FIFO中读数据时,应该将在wclk时钟域中的写指针地址(w_point_g)同步到rclk时钟域中,因此添加了一个两级同步器,最终在rclk时钟域上输出写指针地址(wq_2);并且在读数据时,应该进行判空操作。因此,使用rclk时钟域下的读指针地址(r_point_g)和同步后的写指针地址(wq_2)来判定当前FIFO是否为空;
- 当需要从FIFO中写数据时,应该将在rclk时钟域中的读指针地址(r_point_g)同步到wclk时钟域中,因此添加了一个两级同步器,最终在wclk时钟域上输出读指针地址(rq_2);并且在写数据时,应该进行判满操作。因此,使用wclk时钟域下的写指针地址(w_point_g)和同步后的读指针地址(rq_2)来判定当前FIFO时候已满;
(PS:信号同步器的相关知识可见【Verilog学习日常】—牛客网刷题—Verilog企业真题—VL69-CSDN博客)
//同步信号部分
reg [POINT_WIDTH: 0] rq_1, rq_2;
always @(posedge wclk or negedge wrstn) begin
if (!wrstn ) begin rq_1 <= 'd0; rq_2 <= 'd0; end
else begin rq_1 <=r_point_g; rq_2 <= rq_1; end
end
reg [POINT_WIDTH: 0] wq_1, wq_2;
always @(posedge rclk or negedge rrstn) begin
if (!rrstn ) begin wq_1 <= 'd0; wq_2 <= 'd0; end
else begin wq_1 <=w_point_g; wq_2 <= wq_1; end
end
4.空满检测
异步FIFO的判空与同步FIFO的判空相同,即判断读写指针地址(包含折回标志位)是否完全相同;
异步FIFO的判满:根据格雷码的性质;由于添加了一个折回标志位(最高位);当以01000(15)为对称轴时,可发现前两高位互补,后三低位相同;因此,当读写指针满足该条件时,FIFO队列为满;
//空满检测
assign rempty = (wq_2 == r_point_g) ? 1'b1 : 1'b0;
assign wfull = (w_point_g[POINT_WIDTH] != rq_2[POINT_WIDTH] &&
w_point_g[POINT_WIDTH-1] != rq_2[POINT_WIDTH-1] &&
w_point_g[POINT_WIDTH-2:0] == rq_2[POINT_WIDTH-2:0]) ? 1'b1: 1'b0;
完整代码如下
`timescale 1ns/1ns
/***************************************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 //读数据
);
assign wenc = winc & (~wfull);
assign renc = rinc & (~rempty);
//定义读写指针
parameter POINT_WIDTH = $clog2(DEPTH);
reg [POINT_WIDTH: 0] w_point_b, r_point_b, w_point_g, r_point_g;
//写指针
always @(posedge wclk or negedge wrstn) begin
if (!wrstn) w_point_b <= 'b0;
else begin
if (wenc) w_point_b <= w_point_b + 1'b1; //同步FIFO写法
else w_point_b <= w_point_b;
end
end
//读指针
always @(posedge rclk or negedge rrstn) begin
if (!rrstn) r_point_b <= 'b0;
else begin
if (renc) r_point_b <= r_point_b + 1'b1; //同步FIFO写法
else r_point_b <= r_point_b;
end
end
//读写指针的二进制码转换为格雷码
wire [POINT_WIDTH:0] w_point_g_w, r_point_g_w;
/*
assign w_point_g_w = (w_point_b >> 1) ^ w_point_b;
assign r_point_g_w = (r_point_b >> 1) ^ r_point_b;
always @(posedge wclk or negedge wrstn) begin
if (!wrstn) w_point_g <= 'd0;
else w_point_g <= w_point_g_w;
end
always @(posedge rclk or negedge rrstn) begin
if (!rrstn) r_point_g <= 'd0;
else r_point_g <= r_point_g_w;
end
*/
Bit_To_Gray BG_W ( .Bit_Code(w_point_b), .clk(wclk), .rst_n(wrstn), .Gray_Code(w_point_g_w));
Bit_To_Gray BG_R ( .Bit_Code(r_point_b), .clk(rclk), .rst_n(rrstn), .Gray_Code(r_point_g_w));
always @(*) begin
w_point_g <= w_point_g_w;
r_point_g <= r_point_g_w;
end
//同步信号部分
reg [POINT_WIDTH: 0] rq_1, rq_2;
always @(posedge wclk or negedge wrstn) begin
if (!wrstn ) begin rq_1 <= 'd0; rq_2 <= 'd0; end
else begin rq_1 <=r_point_g; rq_2 <= rq_1; end
end
reg [POINT_WIDTH: 0] wq_1, wq_2;
always @(posedge rclk or negedge rrstn) begin
if (!rrstn ) begin wq_1 <= 'd0; wq_2 <= 'd0; end
else begin wq_1 <=w_point_g; wq_2 <= wq_1; end
end
//空满检测
assign rempty = (wq_2 == r_point_g) ? 1'b1 : 1'b0;
assign wfull = (w_point_g[POINT_WIDTH] != rq_2[POINT_WIDTH] &&
w_point_g[POINT_WIDTH-1] != rq_2[POINT_WIDTH-1] &&
w_point_g[POINT_WIDTH-2:0] == rq_2[POINT_WIDTH-2:0]) ? 1'b1: 1'b0;
//例化双口RAM
dual_port_RAM #(.DEPTH(DEPTH),
.WIDTH(WIDTH))
DR (
.wclk(wclk)
,.wenc(wenc)
,.waddr(w_point_b[POINT_WIDTH-1:0]) //深度对2取对数,得到地址的位宽。
,.wdata(wdata) //数据写入
,.rclk(rclk)
,.renc(renc)
,.raddr(r_point_b[POINT_WIDTH-1:0]) //深度对2取对数,得到地址的位宽。
,.rdata(rdata) //数据输出
);
endmodule
//二进制码转换为Gray码
module Bit_To_Gray
(input [4:0] Bit_Code,
input clk,
input rst_n,
output reg[4:0] Gray_Code);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) Gray_Code <= 'd0;
else Gray_Code <= (Bit_Code >> 1) ^ Bit_Code;
end
endmodule