前言:
FIFO本质为RAM,分为同步FIFO(SCFIFO)和异步FIFO(DCFIFO),前者读写用同一个时钟信号,后者则使用双时钟读写。不过同步FIFO实际运用中较为少(可用做数据缓存),一般多用异步FIFO,因为在FPGA设计中,往往都是多时钟系统,很少为单时钟(除非你单纯做一个流水灯之类的简单实验)。这里,笔者给大家做一个简单的异步FIFO实验,供大家参考。(在看这个实验之前建议大家先学习同步FIFO,文字最后面我会附上同步FIFO的实验链接)
1. 实验内容
了解掌握DCFIFO的工作原理以及其结构,设计DCFIFO模块以及其test_bench,最后VIVADO进行功能实现和仿真验证。
2. 实验原理
在两个时钟域之间进行数据传递最常用的的使用方法就是异步FIFO。异步FIFO一般包括两个端口,其中端口A是写入端,端口B是读入端。AFIFO中最常用的控制信号是“空”(empty)和“满”(full),另外,“将空”(almost empty)、“将满”(almost full)也是两个经常使用的控制信号。
**下图为DCFIFO外部链接关系**
确定DCFIFO的空状态或满状态需要一定的数学处理以及读指针和写指针的比较。关键问题是两个指针在不同的时钟域产生,所以指针必须经过同步化才能在另一时钟域中进行比较和用于计算。对于异步FIFO来说,产生准确的“空”和“满”指示信号是比较困难的,但为了保证操作的安全,避免“满”后仍继续写入和“空”后仍继续读出是可以做到的。
这里为了方便大家理解代码,指针以及满空信号的产生,及为什么要使用格雷码指针,我找了相关资料供大家参考:
以上图片资料来源于百度条词:异步FIFO
简单描述二进制与格雷码转换:
二进制数 —————————— 1 0 1 1 0
(进制数右移1位,空位补 0) — 01 0 1 1
异或运算 —————————— 1 1 1 0 1
这样就可以实现二进制到格雷码的转换了,总结就是移位并且异或,verilog代码实现就一句话:assign rgraynext = (rbinnext>>1) ^ rbinnext;
3. 实验代码
①设计代码
module DCFIFO(
wdata,
wclk,
rinc,
rdata,
wfull,
rempty,
rclk,
rrst_n,
wrst_n,
winc);
input [7:0] wdata;
input wclk;
input rinc;
output [7:0] rdata;
output wfull;
output rempty;
input rclk;
input rrst_n;
input wrst_n;
input winc;
//输入,输出引脚类型定义
wire [7:0] wdata;
wire wclk;
wire rinc;
wire [7:0] rdata;
reg wfull;
reg rempty;
wire rclk;
wire rrst_n;
wire wrst_n;
wire winc;
reg [4:0] wptr, rptr, wq2_rptr, rq2_wptr, wq1_rptr,rq1_wptr;
reg [4:0] rbin, wbin;
reg [7:0] mem[0:(1<<4)-1];
wire[3:0] waddr, raddr;
wire [4:0] rgraynext, rbinnext,wgraynext,wbinnext;
wire rempty_val,wfull_val;
assign rdata=mem[raddr];
always@(posedge wclk)
if (winc && !wfull)
mem[waddr] <= wdata;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
{
wq2_rptr,wq1_rptr} <= 0;
else
{
wq2_rptr,wq1_rptr} <= {
wq1_rptr,rptr};
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
{
rq2_wptr,rq1_wptr} <= 0;
else
{
rq2_wptr,rq1_wptr} <= {
rq1_wptr,wptr};
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
{
rbin, rptr} <= 0;
else
{
rbin, rptr} <= {
rbinnext, rgraynext};
assign raddr = rbin[3:0];
assign rbinnext = rbin + (rinc & ~rempty);
assign rgraynext = (rbinnext>>1) ^ rbinnext; //二进制与格雷码转换
assign rempty_val = (rgraynext == ~rq2_wptr);
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
rempty <= 1'b1;
else
rempty <= rempty_val;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
{wbin, wptr} <= 0;
else
{wbin, wptr} <= {wbinnext, wgraynext};
assign waddr = wbin[3:0];
assign wbinnext = wbin + (winc & ~wfull);
assign wgraynext = (wbinnext>>1) ^ wbinnext;
assign wfull_val = (wgraynext=={~wq2_rptr[4:4-1],
wq2_rptr[4-2:0]});
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
endmodule
设计原理图如下:
②仿真激代码
`timescale 1ns/1ns
module DCFIFO_test();
reg [7:0] wdata;
reg winc;
reg wclk;
reg wrst_n;
reg rinc;
reg rclk;
reg rrst_n;
wire [7:0] rdata;
wire wfull;
wire rempty;
integer i;
always begin
#10 wclk=1;
#10 wclk=0;
end
always begin
#15 rclk=1;
#15 rclk=0;
end
initial begin
wclk=0;rclk=0;wdata=0;winc=0;rinc=0;wrst_n=0;rrst_n=0;i=0;
#2 winc=1;
#2 rinc=0;
for(i=0;i<=20;i=i+1)
begin
repeat(1)@(posedge wclk);
wrst_n=1;
wdata=wdata+1;
end
repeat(1)@(posedge wclk);
wrst_n=0;
#100 rinc=1;
#2 winc=0;
for(i=0;i<=20;i=i+1)
begin
repeat(1)@(posedge rclk);
#2;
rrst_n=1;
end
repeat(1)@(posedge rclk);
#2;
rrst_n=0;
#100 rinc=0;
#2 $finish;
end
//将设计模块例化到仿真模块中
DCFIFO DCFIFO1(
.wdata(wdata),
.wclk(wclk),
.rinc(rinc),
.rdata(rdata),
.wfull(wfull),
.rempty(rempty),
.rclk(rclk),
.rrst_n(rrst_n),
.wrst_n(wrst_n),
.winc(winc));
endmodule
仿真波形图如下:
**后续:**因为时间原因代码的注释还没写,后面如果有时间会补上,并对文章进行完整讲解。
这里推荐一位朋友写的同步FIFO,内容很详细,建议大家看学习异步FIFO之前先看这篇文章:verilog实现FIFO设计(一)之同步8位深度