异步FIFO同步化设计

时间:2022-01-02 04:38:39
代码为《Verilog HDL高级数字设计(第二版)》中的代码,不过中文版其中有些代码缺失,因此又请教了大神。之后把关于信号同步化的异步FIFO设计指导写了下来,感觉可能会用得到。代码里有一些乱码的地方大家用的时候删除掉

一些问题参考:http://blog.csdn.net/moon9999/article/details/77943407


1.第一部分

没有标题了,就一点一点说吧。

首先我们先考虑下这个FIFO的接口,所有的模块设计接口肯定是最重要的。异步FIFO需要输入输入输出什么信号呢?当然是输入复位、读写时钟和读写指示信号,输出为当前的状态信号如是否满了是否空了是否快满了这一类的,因此接口定义如下:

module FIFO_Dual_Port #(
parameter word_width = 32,
stk_ptr_width = 3
)(
output [word_width-1:0] Data_out,
output stk_full,
output stk_almost_full,
output stk_half_full,
output stk_almost_empty,
output stk_empty,

input [word_width-1:0] Data_in,
input write,
input read,
input clk_write,
input clk_read,
input rst
);


这个FIFO的数据宽度是32位,数据深度是8个,从参数上就可以看出来了。输入的信号就不用解释了,输出的信号包括:FIFO全满,FIFO几乎满,FIFO半满,FIFO几乎空和FIFO全空。

定义好接口后,就要考虑内部结构的设计了。按照书中教授的标准设计方式,内部可以分为三个模块:控制单元、状态单元和数据路径。下面结合接口来说一下三个模块。

控制模块(FIFO_Control_Unit)的作用是向数据路径提供当前是要读还是要写的信息。不过既然系统的输入已经有写入读出的信号read和write了,为什么还需要控制模块来提供呢?那是因为不能外边说写就写啊,万一FIFO满了呢?你瞎写就会覆盖掉以前的信息。同理也不能说读就读,有可能FIFO已经空了。所以说控制单元还要接收来自状态单元的FIFO空满的信号,综合考虑时候能够继续写入或者读取数据。接口如下:

module FIFO_Control_Unit (
output write_to_stk, read_fr_stk,
input write, read, stk_full, stk_empty
);

数据路径(FIFO_Datapath_Unit)里面嵌着一组寄存器(或者使用RAM也可以),本次使用的是宽度32位深度8位的寄存器组。他需要接收读写判定和读写指针。也就是说处理要读么,读哪个寄存器的数?要写么,写进哪个寄存器里?因此可以看到起端口如下:Data_in写入时的写入数据,Data_out读出时的读出数据,write_ptr写入时的写入地址,read_ptr读出时的读出地址,write_to_stk写入有效,read_fr_stk读取有效,clk_write写入时钟,clk_read读取时钟,rst复位信号。

module FIFO_Datapath_Unit #(
parameter word_width = 32,
stk_height = 8,
stk_ptr_width = 3
)(
output reg [word_width-1:0] Data_out,
input [word_width-1:0] Data_in,
input [stk_ptr_width-1:0] write_ptr, read_ptr,
input write_to_stk, read_fr_stk, clk_write, clk_read, rst

);


最复杂的是状态单元(FIFO_Status_Unit),他要思考整个FIFO的工作。从控制模块接收信息write_to_stk、read_fr_stk来判断是否要读写,然后把要读写的指针给到数据路径,同时还要向外给出当前寄存器的空满状况。接口如下:

module FIFO_Status_Unit #(
parameter stk_ptr_width = 3,
stk_height = 8,
HF_level = 6,
AF_level = 4,
AE_level = 2
)(
output [stk_ptr_width-1:0] write_ptr,
output [stk_ptr_width-1:0] read_ptr,
output stk_full,
output stk_almost_full,
output stk_half_full,
output stk_empty,
output stk_almost_empty,
input write_to_stk,
input read_fr_stk,
input clk_read,
input clk_write,
input rst
);


综上我们可以得到整体的设计框图了,我就直接从数上摘取下吧。(《Verilog HDL 高级数字设计(第二版)》p383)

异步FIFO同步化设计


2.第二部分

第一部分先把接口都说清楚了,接下来我们一点点的看模块设计。开始的时候就不要想着什么信号的同步化了,我就是要能读能写别覆盖数据或者读跑偏了就好,先不想那么多。那好我们从里面最关键的模块开始说起,状态单元(FIFO_Status_Unit)。


状态单元的工作原理是什么呢?就口述不画状态图了。先把读写状态分开想:

对于读来说,在读时钟clk_read上升沿时候,如果读取信号read_fr_stk有效,那么我需要告诉数据路径:我要读当前读指针read_ptr指着的这个数,你把这个数给Data_out,然后把读指针指到下一个地方去!

对于写来说,在写时钟clk_write上升沿时候,如果写入信号write_to_stk有效,那么我需要告诉数据路径:我要写当前写指针write_ptr值着的这个位置,你把Data_in给我写进去,然后把写指针指到下一个地方去!

因此在这里设置两个指针,用于标记读写位置。

output [stk_ptr_width-1:0] write_ptr,
output [stk_ptr_width-1:0] read_ptr,


一看就非常简单。然后就引发了下一个问题:读写指针不能相互越界啊,不能我还没写你就读到我后面去了,也不能我还没读你就给我写覆盖掉了啊。因此读写指针之间一定是要有关系的。什么关系呢?这就是FIFO空满状态的判定问题。简单来说,如果FIFO空了,那就算来了读信号也是无效信号;如果FIFO是满的,来了写信号也不好使。FIFO的空满就可以通过两个指针的位置来判定啦,不过单纯用两个指针来判定的话比较麻烦,书上说:你把指针扩充一位就好啦!

wire [stk_ptr_width:0] wr_cntr;
wire [stk_ptr_width:0] rd_cntr;


那么指针的跳动就由这两个信号来完成。我们先来看看两个指针怎么调的,状态单元里实例化两个小模块来控制指针的跳转。看下写指针的:

module wr_cntr_Unit #(
parameter stk_ptr_width = 3
)(
output reg [stk_ptr_width:0] wr_cntr,
output [stk_ptr_width-1:0] write_ptr,
input write_to_stk,
input clk_write,
input rst
);

assign write_ptr = wr_cntr[stk_ptr_width-1:0];

always @(posedge clk_write or posedge rst)
begin
if (rst)
wr_cntr <= 0;
else if (write_to_stk)
begin
wr_cntr <= wr_cntr + 1;
end
end

endmodule

是不是感觉没看到过这么简答的程序。wr_cntr是0_000 -> 0_111 -> 1_000 -> 0_000的跳转,那write_ptr 自然就是000 -> 111 -> 000的跳转了,完美符合FIFO循环写入的要求。读取信号也是一样的:

module rd_cntr_Unit #(
parameter stk_ptr_width = 3
)(
output reg [stk_ptr_width:0] rd_cntr,
output [stk_ptr_width-1:0] read_ptr,
input read_fr_stk,
input clk_read,
input rst
);

assign read_ptr = rd_cntr[stk_ptr_width-1:0];

always @(posedge clk_read or posedge rst)
begin
if (rst)
rd_cntr <= 0;
else if (read_fr_stk)
begin
rd_cntr <= rd_cntr + 1;
end
end

endmodule

代码可以说是一样的,如果不想分开写你复用也行。


那么我们接下来看一下如何判定FIFO空还是满。

结合图看表:

异步FIFO同步化设计

时序 write_ptr  read_ptr  距离 wr_cntr   rd_cntr  差值

000 000 0 0000 0000 0000

011 001 2 0011 0001 0010

111 100 3 0111 0100 0011

001 110 3 1001 0110 0011

100 111 5 1100 0111 0101

110 000 6 1110 1000 0110

000 100 4 0000 1100 0100

101 111 6 0101 1111 0110

000 011 5 1000 0011 0101

010 000 2 1010 1000 0010

110 010 4 1110 1010 0100







表里是我随机模拟的读写指针跳转情况,过程中只要保证两个指针不相互越界就可以了,由于读写指针都是循环的引起算起距离来挺不舒服的。不过我们发现使用wr_cntr和rd_cntr算起来就轻松多了,只需要wr_cntr - rd_cntr就可以啦!简直完美。因此计算FIFO的空满状态就有如下代码:
    assign stk_full = ((wr_cntr - rd_cntr) == stk_height) || rst;
assign stk_almost_full = ((wr_cntr - rd_cntr) == AF_level) || rst;
assign stk_half_full = ((wr_cntr - rd_cntr) == HF_level) || rst;
assign stk_almost_empty = ((wr_cntr - rd_cntr) == AE_level) || rst;
assign stk_empty = ((wr_cntr == rd_cntr)) || rst;

这里面几乎满、半满和几乎空的判定条件你自己定吧。

至此,简单的状态单元就写完了,把信号引出来就可以了,完整的代码如下(两个实例化已经在上面写了):
module FIFO_Status_Unit #(
parameter stk_ptr_width = 3,
stk_height = 8,
HF_level = 6,
AF_level = 4,
AE_level = 2
)(
output [stk_ptr_width-1:0] write_ptr,
output [stk_ptr_width-1:0] read_ptr,
output stk_full,
output stk_almost_full,
output stk_half_full,
output stk_empty,
output stk_almost_empty,
input write_to_stk,
input read_fr_stk,
input clk_read,
input clk_write,
input rst
);

wire [stk_ptr_width:0] wr_cntr;
wire [stk_ptr_width:0] rd_cntr;

assign stk_full = ((wr_cntr - rd_cntr) == stk_height) || rst;
assign stk_almost_full = ((wr_cntr - rd_cntr) == AF_level) || rst;
assign stk_half_full = ((wr_cntr - rd_cntr) == HF_level) || rst;
assign stk_almost_empty = ((wr_cntr - rd_cntr) == AE_level) || rst;
assign stk_empty = ((wr_cntr == rd_cntr)) || rst;

wr_cntr_Unit M0 (wr_cntr, write_ptr, write_to_stk, clk_write, rst);
rd_cntr_Unit M1 (rd_cntr, read_ptr, read_fr_stk, clk_read, rst);
endmodule

解决了状态单元的事,控制模块(FIFO_Control_Unit)就很简单了,一看代码就明白了,就是把FIFO的空满状态考虑进去,输出是否应该继续读写的信号就可以了。
module FIFO_Control_Unit (
output write_to_stk, read_fr_stk,
input write, read, stk_full, stk_empty
);

assign write_to_stk = write && (!stk_full);
assign read_fr_stk = read && (!stk_empty);

endmodule

同样,数据路径(FIFO_Datapath_Unit)的代码也一样简单,普通的寄存器组读写而已。
module FIFO_Datapath_Unit #(
parameter word_width = 32,
stk_height = 8,
stk_ptr_width = 3
)(
output reg [word_width-1:0] Data_out,
input [word_width-1:0] Data_in,
input [stk_ptr_width-1:0] write_ptr, read_ptr,
input write_to_stk, read_fr_stk, clk_write, clk_read, rst

);

reg [word_width-1:0] stk[stk_height-1:0];

always @(posedge clk_write)
begin
if (write_to_stk)
stk[write_ptr] <= Data_in;
end

always @(posedge clk_read)
begin
if (read_fr_stk)
Data_out <= stk[read_ptr];
end

endmodule

至此一个简单的异步FIFO就搭建完成了,我们只需要给这三个模块加个壳连起来就OK了。

module FIFO_Dual_Port #(
parameter word_width = 32,
stk_ptr_width = 3
)(
output [word_width-1:0] Data_out,
output stk_full,
output stk_almost_full,
output stk_half_full,
output stk_almost_empty,
output stk_empty,

input [word_width-1:0] Data_in,
input write,
input read,
input clk_write,
input clk_read,
input rst
);

wire [stk_ptr_width-1:0] write_ptr;
wire [stk_ptr_width-1:0] read_ptr;

wire write_to_stk;
wire read_fr_stk;

FIFO_Control_Unit M0_Controller (
.write_to_stk(write_to_stk),
.read_fr_stk(read_fr_stk),
.write(write),
.read(read),
.stk_full(stk_full),
.stk_empty(stk_empty)
);

FIFO_Datapath_Unit M1_Datapath (
.Data_out(Data_out),
.Data_in(Data_in),
.write_ptr(write_ptr),
.read_ptr(read_ptr),
.write_to_stk(write_to_stk),
.read_fr_stk(read_fr_stk),
.clk_write(clk_write),
.clk_read(clk_read),
.rst(rst)
);

FIFO_Status_Unit M2 (
.write_ptr(write_ptr),
.read_ptr(read_ptr),
.stk_full(stk_full),
.stk_almost_full(stk_almost_full),
.stk_half_full(stk_half_full),
.stk_almost_empty(stk_almost_empty),
.stk_empty(stk_empty),
.write_to_stk(write_to_stk),
.read_fr_stk(read_fr_stk),
.clk_write(clk_write),
.clk_read(clk_read),
.rst(rst)
);

endmodule

至于仿真的话,自己写个testbench仿真下就好了,就不在这里写了。


3.第三部分

简单的异步FIFO写好了,我们来继续思考。既然是异步FIFO,那么clk_write和clk_read就很有可能是不一样的,而进行对比的指针wr_cntr和rd_cntr是受到这两个时钟的控制,那么在读写指针同步协作时候(如已满或已空的情况)很有可能会导致接收数据的寄存器的亚稳态,这是不能被允许的。应该如何消除这种亚稳态呢?我们可以通过将信号进行同步后再进行判别当前的FIFO状态:

同步rd_cntr至clk_write时钟域,再与wr_cntr进行对比来决定FIFO是否满;(判断满是wr_cntr - rd_cntr)

同步wr_cntr至clk_read时钟域,再与rd_cntr进行对比来决定FIFO是否空;(判断空是rd_cntr == wr_cntr)

当然了,实际应用中有其他的处理方式,我们在这里不提。


那么在这里就出现了一个很困扰的问题:我们都知道快时钟域的信号向慢时钟域转换是,如果不做握手等相关处理,那么一定会出现信号缺失的情况。那同步过去的rd_cntr(或wr_cntr)指针会不会因为信号缺失最后导致了FIFO空满的判断错误?答案是不会的,理由看下面。

第一种情况是“写快读慢”。那么慢的读指针rd_cntr被clk_write同步时不会出现问题,他慢嘛,所以关于FIFO“满”的判断不会出错。较快的写指针wr_cntr被clk_read同步时候会出现数据丢失情况(注意同步wr_cntr至clk_read时钟域是为了判断FIFO是否为空,这很重要!!)。例如我的wr_cntr已经飞快的由0-1-2-3-...-10了,可是只被clk_read采到了1、5、8三个信号,也就是说在读的时钟域看来wr_cntr是8而他实际是10,那么会有什么后果呢?不会对逻辑判断有影响!因为你wr_cntr跑的越快意味着写进FIFO的数越多,就离“空”越远!换句话讲,wr_cntr是8都不空的话,实际为10就更加不可能是空了,所以关于FIFO“空”的判断不会出错。

另一种情况是“写慢读快”。那么慢的写指针wr_cntr被clk_read同步时不会出现问题,他慢嘛,所以关于FIFO“空”的判断不会出错。较快的读指针rd_cntr被clk_write同步时候会出现数据丢失情况(注意同步rd_cntr至clk_write时钟域是为了判断FIFO是否为满,这很重要!!)。还是举个栗子,rd_cntr从0一路飞奔到了12,而我的写时钟只采样到了2、6、9。那我会把9拿来和wr_cntr做对比来判断FIFO时候满了。你想想看,如果9就不满的话,那我实际读到了12就跟不会使得FIFO变“满”了(一定要注意,什么是满了呢?就是写指针追上读指针了,那么读指针在你不知道的情况下多跑了好几步,就更不会满了)。因此关于FIFO“满”的判断也不会出错。

综上,这样的同步时没有问题的。注意我们的要求是:漏掉的地址没有对FIFO的逻辑操作产生影响。本次设计汇总写入时钟为100MHz,读取时钟为133MHz,属于“写慢读快”。


在进行同步之前,我们要记得,在这种异步FIFO的跨时钟域同步的操作中,传过去的计数器指针一般采用格雷码。为什么采用格雷码呢?因为格雷码每次相邻数值改变只会跳变一位。摘取一段别人的解释,地址如下,侵权必删。http://www.cnblogs.com/kxk_kxk/p/3931591.html

“我们可以对异步FIFO的地址采用binary编码,这样并不影响异步FIFO的功能,前提是读写地址同步时能够保持正确。这种情况在功能仿真时完全正确,问题只有到时序仿真时才会遇到。毛刺可以说是异步电路的杀手,一个毛刺被触发器采样后会被放大,然后传播,导致电路功能出错。binary编码的地址总线在跳变时极易产生毛刺,因为binary编码是多位跳变,在实现电路时不可能做到所有的地址总线等长,address bus skew必然存在,而且写地址和读地址分属不同时钟域,读写时钟完全异步,这样地址总线在进行同步过程中出错不可避免,比如写地址在从0111到1000转换时4条地址线同时跳变,这样读时钟在进行写地址同步后得到的写地址可能是0000-1111的某个值,这个完全不能确定,所以用这个同步后的写地址进行FIFO空判断的时候难免出错。

这个时候gray码体现了价值,一次只有一位数据发生变化,这样在进行地址同步的时候,只有两种情况:1.地址同步正确;2.地址同步出错,但是只有1位出错;第一种正确的情况不需要分析,我们关注第二种,假设写地址从000->001,读时钟域同步出错,写地址为000->000,也就是地址没有跳变,但是用这个错误的写地址去做空判断不会出错,最多是让空标志在FIFO不是真正空的时候产生,而不会出现空读的情形。所以gray码保证的是同步后的读写地址即使在出错的情形下依然能够保证FIFO功能的正确性,当然同步后的读写地址出错总是存在的(因为时钟异步,采样点不确定)。这里需要注意gray码只是在相邻两次跳变之间才会出现只有1位数据不一致的情形,超过两个周期则不一定,所有地址总线bus skew一定不能超过一个周期,否则可能出现gray码多位数据跳变的情况,这个时候gray码就失去了作用,因为这时候同步后的地址已经不能保证只有1位跳变了。”

因此我们知道了,在进行数据同步之前,首先要讲二进制计数指针wr_cntr和wr_cntr转换为格雷码。


4.第四部分

怎么讲二进制的计数器转换为格雷码计数器呢?其实简单的办法就是先让他按照二进制那样跳变,然后出来的结果转化成格雷码再去同步就好了。二进制-格雷码的转换网上教程多的很,我就不赘述了也可以看我的另外一个博客。直接上程序吧。

二进制转格雷码:

module B2G_Conv #(parameter size = 4)(
output [size-1:0] gray,
input [size-1:0] binary
);

assign gray = (binary >> 1) ^ binary;

endmodule

格雷码转二进制:

module G2B_Conv #(parameter size = 4)(
output reg [size-1:0] binary,
input [size-1:0] gray
);

integer k;
always @(gray)
begin
for (k = 0; k < size; k = k + 1)
binary[k] = ^(gray >> k);
end

endmodule


好的,那么转换出来的格雷码就可以拿去同步啦。再次记得我们现在做这些的目的:我要把写指针wr_cntr同步到clk_write时钟域去,为了同步不出问题我先把wr_cntr由二进制转化为格雷码,然后同步过去,之后再把它由格雷码转化成二进制码去进行对比!!同理rd_cntr也是这么做!!一点也不复杂啊,默念。


下面的问题就是怎么同步呢?由快时钟域向慢时钟域同步和由慢时钟域向快时钟域同步的结构是完全不同的,见《Verilog HDL高级数字设计(第二版)》的130-132页相关内容,一会我再另外的博客中说明下。在这里直接上代码,你知道这是在同步就可以了,注意同步操作是一位一位进行的,指针有几位就要有实例化多少个同步模块,在实际代码中用了for循环结构。

慢时钟域信号向快时钟域同步:

module Synchro_Long_Asynch_in_to_Short_Period_Clock (
output reg Synch_out,
input Synch_in, clock, reset
);

reg Synch_meta;

always @(posedge clock or posedge reset)
begin
if (reset)
begin
Synch_meta <= 0;
Synch_out <= 0;
end
else
{Synch_out, Synch_meta} <= {Synch_meta, Synch_in};
end

endmodule

快时钟域向慢时钟域同步:

module Synchro_Short_Asynch_in_to_Long_Period_Clock (
output reg Synch_out,
input Synch_in, clock, reset
);


reg q1, q2;
supply1 Vcc;
wire Clr_q1_q2 = reset || (!Synch_in && Synch_out);

always @(posedge clock or posedge reset)
begin
if (reset)
begin
Synch_out <= 0;
end
else
Synch_out <= q2;
end

always @(posedge clock or posedge Clr_q1_q2)
begin
if (Clr_q1_q2)
q2 <= 0;
else
q2 <= q1;
end

always @(posedge Synch_in or posedge Clr_q1_q2)
begin
if (Clr_q1_q2)
q1 <= 0;
else begin
q1 <= Vcc;
end
end

endmodule

好,完成了同步设计就快要完成了。

5.第五部分

让我们再明确下我们在FIFO_Status_Unit 状态单元里做什么。

写指针根据写有效和写时钟跳变——写指针转换为格雷码——被读时钟同步——写格雷码指针转换为二进制——与读指针对比来判定FIFO是否“空”;

读指针根据读有效和读时钟跳变——读指针转换为格雷码——被写时钟同步——读格雷码指针转换为二进制——与写指针对比来判定FIFO是否“满”;

只要这里没乱,下面就不会有问题。我们开看看状态单元的完整代码:

module FIFO_Status_Unit #(
parameter stk_ptr_width = 3,
stk_height = 8,
HF_level = 6,
AF_level = 4,
AE_level = 2
)(
output [stk_ptr_width-1:0] write_ptr,
output [stk_ptr_width-1:0] read_ptr,
output stk_full,
output stk_almost_full,
output stk_half_full,
output stk_empty,
output stk_almost_empty,
input write_to_stk,
input read_fr_stk,
input clk_read,
input clk_write,
input rst
);

wire [stk_ptr_width:0] wr_cntr, next_wr_cntr;
wire [stk_ptr_width:0] wr_cntr_G;
wire [stk_ptr_width:0] rd_cntr, next_rd_cntr;
wire [stk_ptr_width:0] rd_cntr_G;
wire [stk_ptr_width:0] wr_cntr_G_sync, rd_cntr_G_sync;
wire [stk_ptr_width:0] wr_cntr_B_sync, rd_cntr_B_sync;

// Stack status signals
assign stk_full = ((wr_cntr - rd_cntr_B_sync) == stk_height) || rst;
assign stk_almost_full = ((wr_cntr - rd_cntr_B_sync) == AF_level) || rst;
assign stk_half_full = ((wr_cntr - rd_cntr_B_sync) == HF_level) || rst;
assign stk_almost_empty = ((wr_cntr - rd_cntr_B_sync) == AE_level) || rst;
assign stk_empty = ((wr_cntr_B_sync == rd_cntr)) || rst;

wr_cntr_Unit M0_rw_cntr (next_wr_cntr, wr_cntr, write_ptr, write_to_stk, clk_write, rst);

rd_cntr_Unit M1_rd_cntr (next_rd_cntr, rd_cntr, read_ptr, read_fr_stk, clk_read, rst);

B2G_Reg M2_B2G (
.gray_out(wr_cntr_G),
.binary_in(next_wr_cntr),
.wr_rd(write_to_stk),
.limit(stk_full),
.clk(clk_write),
.rst(rst)
);

G2B_Conv M3_G2B (
.binary(wr_cntr_B_sync),
.gray(wr_cntr_G_sync)
);

B2G_Reg M4_B2G (
.gray_out(rd_cntr_G),
.binary_in(next_rd_cntr),
.wr_rd(read_fr_stk),
.limit(stk_empty),
.clk(clk_read),
.rst(rst)
);

G2B_Conv M5_G2B (
.binary(rd_cntr_B_sync),
.gray(rd_cntr_G_sync)
);
genvar i;
generate
for (i = 0; i <= stk_ptr_width; i = i + 1)
begin:write_cntr_synchronization
Synchro_Long_Asynch_in_to_Short_Period_Clock M0 (
.Synch_out(wr_cntr_G_sync[i]),
.Synch_in(wr_cntr_G[i]),
.clock(clk_read),
.reset(rst)
);
end
endgenerate

generate
for (i = 0; i <= stk_ptr_width; i = i + 1)
begin:read_cntr_synchronization
Synchro_Short_Asynch_in_to_Long_Period_Clock M1 (
.Synch_out(rd_cntr_G_sync[i]),
.Synch_in(rd_cntr_G[i]),
.clock(clk_write),
.reset(rst)
);
end
endgenerate

endmodule

发现了吧,与不带同步的状态单元对比,只是多了二进制-格雷码的相互转换以及数据同步而已,应该是不难的。其中的B2G_Reg代码如下,就是给之前的代码裹了一层衣服,延了个一周期并且引入判断条件:

module B2G_Reg #(parameter size = 4)(
output reg [size-1:0] gray_out,
input [size-1:0] binary_in,
input wr_rd, limit, clk, rst
);

wire [size-1:0] next_gray_out;
always @(posedge clk or posedge rst)
begin
if (rst)
gray_out <= 0;
else if (wr_rd && (!limit))
gray_out <= next_gray_out;
end

B2G_Conv M0 (
.gray(next_gray_out),
.binary(binary_in)
);


endmodule

至此,最为关键的FIFO_Status_Unit 状态单元设计完成。


6.第六部分

令人幸福的是,控制单元和数据路径与之前是一样的~当然了整体结构也没有变化,因此在最后就把完成的异步FIFO代码附在这里包括testbench,对了根据书中的设计,FIFO的数据输入由一个串并转换器Ser_Par_Conv_32 来提供,串行输入32位并行输出,所以代码里也有他,建立工程时候就看到了。

module tb_FIFO_Channel();

parameter word_width = 32,
half_cycle_100_MHz = 4,
half_cycle_133_Mhz = 3;

wire [word_width-1:0] Data_out_FIFO;
wire ready;
reg En, read, clk_write, clk_read, rst;

FIFO_Channel M0 (Data_out_FIFO, ready, Data_in, En, read, clk_write, clk_read, rst);

initial
#8000 $stop;

initial
begin
clk_write = 0;
forever #half_cycle_133_Mhz clk_write = ~clk_write;
end

initial
begin
clk_read = 0;
forever #half_cycle_100_MHz clk_read = ~clk_read;
end

initial
fork
En = 0;
#18 En = 1;
#400 En = 0;
#960 En = 1;
join

initial
fork
read = 0;
#3500 read = 1;
#4200 read = 0;
#7124 read = 1;
join

initial
fork
#0 rst = 1;
#2 rst = 0;
#5050 rst = 1;
#5075 rst = 0;
join

reg [word_width-1:0] Pattern_buffer[15:0];
reg [3:0] Pattern_ptr;
reg [word_width-1:0] Data_word;

assign Data_in = Data_word[0];

always @(negedge clk_write or posedge rst)
begin
if (rst)
begin
Pattern_ptr <= 1;
Data_word <= Pattern_buffer[0];
end
else
begin
if (M0.M1.pause_full)
begin
Data_word <= Pattern_buffer[Pattern_ptr-1];
end
if (M0.M1.pause_En_b)
begin
Data_word <= Pattern_buffer[Pattern_ptr];
end
if (M0.M0.write_to_stk)
begin
Pattern_ptr <= Pattern_ptr + 1;
Data_word <= Pattern_buffer[Pattern_ptr];
end
else if ((M0.M1.shft_incr)&&(M0.M1.M0_Controller.state != M0.M1.M0_Controller.S_idel))
begin
Data_word <= Data_word >> 1;
end

end
end


initial
fork
Pattern_buffer[0] = 32'haaaa_aaaa;
Pattern_buffer[1] = 32'hbbbb_bbbb;
Pattern_buffer[2] = 32'hcccc_cccc;
Pattern_buffer[3] = 32'hdddd_dddd;
Pattern_buffer[4] = 32'heeee_eeee;
Pattern_buffer[5] = 32'hffff_ffff;
Pattern_buffer[6] = 32'haaaa_ffff;
Pattern_buffer[7] = 32'hbbbb_aaaa;
Pattern_buffer[8] = 32'ha5a5_5a5a;
Pattern_buffer[9] = 32'hb5b5_5b5b;
Pattern_buffer[10] = 32'hcccc_5555;
Pattern_buffer[11] = 32'hdddd_5555;
Pattern_buffer[12] = 32'heeee_5555;
Pattern_buffer[13] = 32'hffff_5555;
Pattern_buffer[14] = 32'haaaa_5555;
Pattern_buffer[15] = 32'hbbbb_5555;
join


endmodule
module FIFO_Channel #(    parameter word_width = 32    )(    output [word_width-1:0] Data_out_FIFO,    output ready,    input Data_in, En, read, clk_write, clk_read, rst     );wire [word_width-1:0] Data_out_Ser_Par;wire Data_in_Ser_Par;wire stk_full, stk_almost_full, stk_half_full, stk_almost_empty, stk_empty, write;FIFO_Dual_Port M0 (    .Data_out(Data_out_FIFO),    .stk_full(stk_full),    .stk_almost_full(stk_almost_full),    .stk_almost_empty(stk_almost_empty),    .stk_empty(stk_empty),    .Data_in(Data_out_Ser_Par),    .write(write),    .read(read),    .clk_write(clk_write),    .clk_read(clk_read),    .rst(rst)    );Ser_Par_Conv_32 M1 (    .Data_out(Data_out_Ser_Par),    .ready(ready),    .write(write),    .Data_in(Data_in),    .En(En),    .full(stk_full),    .clk(clk_write),    .rst(rst)    );endmodule

module FIFO_Dual_Port #(
parameter word_width = 32,
stk_ptr_width = 3
)(
output [word_width-1:0] Data_out,
output stk_full,
output stk_almost_full,
output stk_half_full,
output stk_almost_empty,
output stk_empty,

input [word_width-1:0] Data_in,
input write,
input read,
input clk_write,
input clk_read,
input rst
);

wire [stk_ptr_width-1:0] write_ptr;
wire [stk_ptr_width-1:0] read_ptr;

wire write_to_stk;
wire read_fr_stk;

FIFO_Control_Unit M0_Controller (
.write_to_stk(write_to_stk),
.read_fr_stk(read_fr_stk),
.write(write),
.read(read),
.stk_full(stk_full),
.stk_empty(stk_empty)
);

FIFO_Datapath_Unit M1_Datapath (
.Data_out(Data_out),
.Data_in(Data_in),
.write_ptr(write_ptr),
.read_ptr(read_ptr),
.write_to_stk(write_to_stk),
.read_fr_stk(read_fr_stk),
.clk_write(clk_write),
.clk_read(clk_read),
.rst(rst)
);

FIFO_Status_Unit M2 (
.write_ptr(write_ptr),
.read_ptr(read_ptr),
.stk_full(stk_full),
.stk_almost_full(stk_almost_full),
.stk_half_full(stk_half_full),
.stk_almost_empty(stk_almost_empty),
.stk_empty(stk_empty),
.write_to_stk(write_to_stk),
.read_fr_stk(read_fr_stk),
.clk_write(clk_write),
.clk_read(clk_read),
.rst(rst)
);

endmodule




module FIFO_Datapath_Unit #(    parameter   word_width = 32,                stk_height = 8,                stk_ptr_width = 3    )(    output reg [word_width-1:0] Data_out,    input [word_width-1:0] Data_in,    input [stk_ptr_width-1:0] write_ptr, read_ptr,    input write_to_stk, read_fr_stk, clk_write, clk_read, rst    );    reg [word_width-1:0] stk[stk_height-1:0];    always @(posedge clk_write)    begin        if (write_to_stk)            stk[write_ptr] <= Data_in;    end    always @(posedge clk_read)    begin        if (read_fr_stk)            Data_out <= stk[read_ptr];    endendmodule
module FIFO_Status_Unit #(    parameter   stk_ptr_width = 3,                stk_height = 8,                HF_level = 6,                AF_level = 4,                AE_level = 2    )(    output [stk_ptr_width-1:0] write_ptr,    output [stk_ptr_width-1:0] read_ptr,    output stk_full,    output stk_almost_full,    output stk_half_full,    output stk_empty,    output stk_almost_empty,    input write_to_stk,    input read_fr_stk,    input clk_read,    input clk_write,    input rst    );    wire [stk_ptr_width:0] wr_cntr, next_wr_cntr;    wire [stk_ptr_width:0] wr_cntr_G;    wire [stk_ptr_width:0] rd_cntr, next_rd_cntr;    wire [stk_ptr_width:0] rd_cntr_G;    wire [stk_ptr_width:0] wr_cntr_G_sync, rd_cntr_G_sync;    wire [stk_ptr_width:0] wr_cntr_B_sync, rd_cntr_B_sync;    // Stack status signals    assign stk_full = ((wr_cntr - rd_cntr_B_sync) == stk_height) || rst;    assign stk_almost_full = ((wr_cntr - rd_cntr_B_sync) == AF_level) || rst;    assign stk_half_full = ((wr_cntr - rd_cntr_B_sync) == HF_level) || rst;    assign stk_almost_empty = ((wr_cntr - rd_cntr_B_sync) == AE_level) || rst;    assign stk_empty = ((wr_cntr_B_sync == rd_cntr)) || rst;    wr_cntr_Unit M0_rw_cntr (next_wr_cntr, wr_cntr, write_ptr, write_to_stk, clk_write, rst);    rd_cntr_Unit M1_rd_cntr (next_rd_cntr, rd_cntr, read_ptr, read_fr_stk, clk_read, rst);    B2G_Reg M2_B2G (        .gray_out(wr_cntr_G),        .binary_in(next_wr_cntr),        .wr_rd(write_to_stk),        .limit(stk_full),        .clk(clk_write),        .rst(rst)        );    G2B_Conv M3_G2B (        .binary(wr_cntr_B_sync),        .gray(wr_cntr_G_sync)        );    B2G_Reg M4_B2G (        .gray_out(rd_cntr_G),        .binary_in(next_rd_cntr),        .wr_rd(read_fr_stk),        .limit(stk_empty),        .clk(clk_read),        .rst(rst)        );    G2B_Conv M5_G2B (        .binary(rd_cntr_B_sync),        .gray(rd_cntr_G_sync)        );    genvar i;    generate        for (i = 0; i <= stk_ptr_width; i = i + 1)        begin:write_cntr_synchronization            Synchro_Long_Asynch_in_to_Short_Period_Clock M0 (                .Synch_out(wr_cntr_G_sync[i]),                .Synch_in(wr_cntr_G[i]),                .clock(clk_read),                .reset(rst)                );        end    endgenerate    generate        for (i =  0; i <= stk_ptr_width; i = i + 1)        begin:read_cntr_synchronization            Synchro_Short_Asynch_in_to_Long_Period_Clock M1 (                .Synch_out(rd_cntr_G_sync[i]),                .Synch_in(rd_cntr_G[i]),                .clock(clk_write),                .reset(rst)                );        end    endgenerate    endmodule
module wr_cntr_Unit #(    parameter stk_ptr_width = 3    )(    output reg [stk_ptr_width:0] next_wr_cntr, wr_cntr,    output [stk_ptr_width:0] write_ptr,    input write_to_stk,    input clk_write,    input rst    );    assign write_ptr = wr_cntr[stk_ptr_width-1:0];    always @(posedge clk_write or posedge rst)    begin        if (rst)            wr_cntr <= 0;        else if (write_to_stk)        begin            wr_cntr <= next_wr_cntr;        end    end    always @(wr_cntr)    begin        next_wr_cntr = wr_cntr + 1;    endendmodule
module rd_cntr_Unit #(    parameter stk_ptr_width = 3    )(    output reg [stk_ptr_width:0] next_rd_cntr, rd_cntr,    output [stk_ptr_width:0] read_ptr,    input read_fr_stk,    input clk_read,    input rst    );    assign read_ptr = rd_cntr[stk_ptr_width-1:0];    always @(posedge clk_read or posedge rst)    begin        if (rst)            rd_cntr <= 0;        else if (read_fr_stk)        begin            rd_cntr <= next_rd_cntr;        end    end    always @(rd_cntr)    begin        next_rd_cntr = rd_cntr + 1;    endendmodule
剩下的几个在第三、四、五部分里就有。

串并转换模块:

module Ser_Par_Conv_32 #(parameter word_width = 32)(
output [word_width-1:0] Data_out,
output ready,
output write,
input Data_in, En, full, clk, rst
);

wire pause_full, pause_En_b, shft_incr, cntr_limit;

Control_Unit M0_Controller (ready, write, pause_full, pause_En_b, shft_incr, En, full, cntr_limit, clk, rst);

Datapath_Unit M1_Datapath (Data_out, cntr_limit, Data_in, pause_full, pause_En_b, shft_incr, clk, rst);

endmodule
module Control_Unit (    output ready, write,    output reg pause_full, pause_En_b, shft_incr,    input En, full, cntr_limit, clk, rst    );    parameter   S_idel = 0,                S_1 = 1,                S_2 = 2;    reg [1:0] state, next_state;    assign ready = (state == S_idel);    assign write = (state == S_2);    always @(posedge clk or posedge rst)    begin        if (rst)            state <= S_idel;        else             state <= next_state;    end    always @(state, En, full, cntr_limit)    begin        pause_full = 0;        pause_En_b = 0;        shft_incr = 0;        next_state = S_idel;        case (state)        S_idel:        begin            if (En && (!full))            begin                next_state = S_1;                shft_incr = 1;            end            else                 next_state = S_idel;        end        S_1:        begin            if (full)            begin                next_state = S_idel;                pause_full = 1;            end            else            begin                shft_incr = 1;                if (cntr_limit)                    next_state = S_2;                else begin                    next_state = S_1;                end            end        end        S_2:        begin            if (En)            begin                shft_incr = 1;                next_state = S_1;            end            else begin                pause_En_b = 1;                next_state = S_idel;            end        end        default        begin            next_state = S_idel;        end        endcase    endendmodule
module Datapath_Unit #(parameter word_width = 32, cntr_width = 5)(    output reg [word_width-1:0] Data_out,    output cntr_limit,     input Data_in, pause_full, pause_En_b, shft_incr, clk, rst    );    reg [cntr_width-1:0] cntr;    always @(posedge clk or posedge rst)    begin        if (rst)        begin            cntr <= 0;        end        else if (pause_full || pause_En_b)            cntr <= 0;        else if (shft_incr)            cntr <= cntr + 1;    end    always @(posedge clk or posedge rst)    begin        if (rst)        begin            Data_out <= 0;        end        else if (pause_full || pause_En_b)         begin            Data_out <= 0;        end        else if (shft_incr)            Data_out = {Data_in, Data_out[word_width-1:1]};    end    assign cntr_limit = (cntr == word_width-1);endmodule