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