异步FIFO的实现方式
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
异步FIFO的实现方式
实验目的
本次实验介绍一种异步FIFO的实现方式。
使用FIFO存储器可以在两个不同时钟系统之间快速而方便的传输数据。
另外,在网络接口,图像处理等方面异步FIFO存储器也得到了广泛的应用。
因此,异步FIFO存储器具有较大的研究和应用价值。
异步FIFO的介绍和整体结构
异步FIFO(First In First Out)存储器是指向FIFO缓冲器中写入数据的时钟域和从FIFO缓冲器中读取数据的时钟域是不同的,这两个时钟之间没有必然的因果关系。
异步FIFO是一种先进先出的电路,使用在异步时钟域数据接口的部分,用来存储、缓冲在两个异步时钟之间的数据传输。
在异步电路中,由于时钟之间周期和相位完全独立,所以数据的丢失概率不为零。
如何设计一个高可靠性、高速的异步FIFO存储器便成为一个难点。
异步FIFO的一般结构如图1所示,都是由一个读时钟域电路、一个写时钟域电路和一个双端口的RAM来构成的。
异步FIFO与同步FIFO所做的工作是相同的,都是在写信号有效时写数据到RAM中,在读信号有效时把数据从RAM中读出,所以对于中间部分的RAM 设计是比较简单的。
另外,读电路和写电路单独实现起来也是比较容易的,只需要按照同步FIFO的工作情况,如果没有写满或读空的状态时每写一个数据就把写地址加1,每读一个数据就把读地址减1。
设计难点在于两个时钟域的交叠部分:满、空状态的产生,这也是设计的重点。
图1 异步FIFO结构
针对这个问题,先从对亚稳态的处理开始介绍
亚稳态的处理
一个触发器进入亚稳态时,既无法预测该单元的输出电平,也无法预测何时输出才能稳定在某个正确的电平上。
在这个稳定期间,触发器输出一些中间级电平,或者可能处于振荡状态、并且这种无用的输出电平可以沿信号通道上的各个触发器级联式传播下去。
亚稳态发生的原因是由于在同步系统中,如果触发器的建立时间或保持时间不满足,就可能产生亚稳态,此时触发器输出端Q在亚稳态是指触发器无法在某个规定时间段内达到一个可确认的状态,逻辑误判有可能通过电路的特殊设计减轻危害(如本设计中将使用的Gray码计数器),而亚稳态的传播则扩大了故障面,难以处理。
在数字集成电路中寄存器要满足建立时间和保持时间。
建立时间是在时钟翻转之前数据输入必须有效的时间,保持时间是在时钟沿之后数据输出必须仍然有效的时间。
当一个信号被寄存器锁存时,如果信号和时钟之间不满足这个要求,Q的值是不确定的,并且在未知的时刻会固定到高电平或低电平。
此时寄存器进入了亚稳态(Metastability)。
解决这一问题的最简单方法是使用同步器,使得在另一个时钟域采样时信号足够稳定。
同步器的设计本身就是一个比较麻烦的问题,本次设计中也不深入讨论一些细节性的问题,直接采用两级采样的同步器,避免了使用一级同步器仍可能出现亚稳态的情况。
每个这样的同步器都具有一个等于时钟周期的等待时间。
这种同步器可以把一些亚稳态的值同步为确定值,但并不一定是正确值,同时有一些亚稳态也还是无法稳定成确切值的,这种情况称为同步出错。
由于同步出错的随机性,很难对它们进行跟踪。
如果想进一步降低亚稳态出现的概率、可以再増加同步器的级数,但是太多的同步器会使系统的性能下降,所以系统中不会用太多的同步器,一般使用两个同步器已经足够。
空满状态的判断
之所以在前面介绍了亚稳态的问题,是因为这是判断满状态或空状态无法回避的一个问题。
因为读电路在读控制时维持一个地址指针,写电路在写控制时维持一个地址指针,简单来说,这两个地址指针直接一比较,就能得到空满的判断结果,但是实际操作起来非常麻烦。
例如对于满状态来说,这是写入电路所关心的状态,因为满状态下不能继续写入数据,但是空状态对于写电路没有影响。
如果写入电路要判断当前FIFO是否为满,就需要把写电路自身维持的写指针和读电路维持的读指针做比较,这个读指针就需要送入写电路中,此时就发生了穿过时钟域的问题,也就是说,读指针要从读时钟域同步到写时钟域,然后参与判断,此时就需要前面介绍的同步器。
同样,对于空状态来说,这是读出电路所关心的状态,也是由读电路来维持的,因为空状态下再读数就会得到错误的数据,但是满状态下读数是没有影响的。
如果读电路要判断当前FIFO是否为空,就需要把写时钟域中的写指针取到读时钟域来,和读时钟域的读指针进行比较得出是否是空状态,同样跨越了时钟域。
在跨时钟域系统中希望出现错误的概率越低越好,此时格雷码无疑是最好的一个选择。
格雷码属于可靠性编码,是一种误差最小化的编码,它大大减少了由一个状态到下一个状态时电路混淆。
由这种编码相邻的两个码组之间只有一位不同,和其他编码同时改变2位和多位的情况相比更为可靠。
表1所示是格雷码与二进制码的对应关系。
表1 格雷码与二进制码转换真值表
由前面的介绍可知通过同步器之后信号稳定的值可能是1也可能是0,可能与输入的值相同也可能与输入的值不同。
如果对于二进制码,这显然是灾难性的。
例如从十进制的7变到8,二进制码是从0111变为1000,把0111送入同步器之后,由于4位都要变化,所以4位都可能会出现亚稳态,从而在同步器的输出端就会出现各种可能性,这样即使数据稳定下来,对整个电路的作用也很小。
而如果采用格雷码,是从0100变为1100,只是最高位发生了改变,也就只有这一位可能会出现亚稳态的情况。
这样经过同步器处理之后,输出端可能得到的值只有两种0100或1100,其中1100是正确的数值,如果得到这个输出自然是最好,但即使是0100的输出,也只是和原来的值相同,可以认为没有变化,这也不会对电路造成负面的影响。
相比二进制代码那种变化后什么值都有可能的情况,格雷码显然是一种更易于接受的编码方式。
格雷码虽然在跨时钟域方面效果比较好,但在本身计数方面是不足的,也就是说还需要把格雷码转换成二进制码来计数,4位的格雷码转二进制码的代码部分如下:bin[0]=gray[3]^gray[2]^ gray[1]^ gray[0];
bin[1]=gray[3]^gray[2]^ gray[1];
bin[2]=gray[3]^gray[2];
bin[2]=gray[3];
也可以用for循环完成:
module gray[2](bin,gray);
parameter size=4;
output [size-1:0] bin;
input [size-1:0] gray;
reg [size-1:0] bin;
integer i;
always @(gray)
for(i=0;i,size;i=i=i+1);
bin[i]=^(gray>>1);
endmodule
计数之后还要变回格雷码,转换的方法与上述方式类似。
这样使用格雷码作为指针就可以降低亚稳态带来的影响。
接下来要解决的是空满判断的问题,常用的判断方法是附加位比较法。
附加位比较法是给每个指针增加一个附加位,对于二进制指针而言,将存储空间的最后一个存储单元写入数据后,地址将变为零,即地址指针低n-1位清零并向最高位(MSB)也就是附加位进位。
读指针也是如此工作。
如果两个指针的最高位(MSBs)不同而其余位相同,就说明写指针比读指针多循环了一次,标志FIFO存储器处于满状态。
如果包括最高位在内的两个指针完全相同,则说明写指针和读指针经历了相同次数的循环,也就是说FIFO存储器处于空状态。
这样读指针和写指针就变成了一个n位指针,其中低n-1位是用来存放FIFO 存储器的地址,可以用来对2n-1个存储单元寻址,而最高位则用来辨别当两个指针的地址相等时是满状态还是空状态。
对二进制指针来说,用这种方式来区分满状态与空状态是可行的。
但是,格雷码指针却不能直接使用这种方式,原因有两个。
举个4位格雷码的例子,格雷码计数器的低3位用于存放存储地址,第四位是附加位,这个FIFO存储器的存储容量为8。
正确的操作应当是,当写(或读)完一个循环时,地址应该重新开始计数,附加位应该翻转。
然而格雷码指针却并非如此,地址由7到8格雷码由0-100到1-100),指针的附加位改变,但是地址位(低n-1位)却没有重新开始计数,这是由于格雷码是一种镜像码造成的。
第二个原因是这种格雷码不能直接产生满状态标志。
如果两个格雷码指针都是Gray-7,这时的FIFO存储器为空状态,在进行一次写操作后写指针将加1,格雷码第4位将变为1而低3位不变,这时的读指针和写指针的最高位将不同而低位相同。
如果这样的话,FIFO 存储器满标志将置位,这显然是错误的,因而需要对这个4位的格雷码进行修改。
想要的结果是:一个n位的(即包括附加位)格雷码计数器用在异步时钟域间传递数据,但是又希望它的低n-1位计数器也是格雷码类型的。
这样低n-1位就能单独形成一个循环,而不是一种反射码。
所以,此时需要的是一个既能产生n位的格雷码序列又能产生n-1位的格雷码序列的计数器。
分别实现一个n位的格雷码计数器和一个n-1位的格雷码计数器自是非常简单:用一个计数器来实现一个n位的格雷码计数器,并将这个计数器的次高位进行修改而低位保持不变以实现一个n-1位的格雷码计数器,这也不是一件很难的事情。
这种既能产生n位格雷码又能产生n-1位格雷码的计数器被称为"两重格雷码计数器。
下面以3位和4位格雷码来说明空满状态的判断标准。
3位格雷码表示的就是地址空间,可以有8个存储空间。
由于写入和读出并不是按照从000开始的,而是可以以任意一个位置开始,比如存放数据可以按照十进制地址5、6、7、0、1、2、3、4的地址顺序来存放,读出数据也同理,这样为了表示循环,就增加了1位变为4位格雷码。
首先说明空状态的判断标准,空状态表示读指针和写指针重合,此时无论是看3位格雷码还是4位格雷码都应该是完全相同,比如写指针指向1010,读指针也必然指向1010,这样判断空状态就只需要判断两个指针是否相同,相同时即为空,不同时即为不空。
然后解释满状态的判断标准。
满状态判断比较复杂。
假设一次写入数据是从十进制地址6开始,连续写入8个数据。
到14,这时存满8个数据,应产生满状态输出,这两个地址形式如下:
如果是二进制地址,判断的方法已经介绍过了。
而格雷码地址的前两位是不同的,但后面的两位是相同的。
如果扩展成更多位的格雷码,满状态下依然是这种情况,即前两位不同,后面位均相同。
这样判断满状态首先要保证除去前两位之后的剩余部分是相同的。
然后对于本例来说,需要保证前两部分是01和10,如果地址是以01开头,则满时一
定是10;如果以10开头,满时一定是01。
判断的方法可以有很多种,这里采用先取前两位的异或值,保证相等,此时只可能是0,1的组合,然后再判断首位不同,这样就只能是01和10这两种情况。
经过这三个条件的判断,就能就保证此时为写满状态。
再观察地址以00和11开头的情况,给出地址如下
刚才提出的的三个条件依然保证写满状态的正常输出,所以写满状态就以这三个条件作判断标准。
下面开始具体设计:
顶层模块的端口和功能
针对异步FIFO的基本结构和功能,以及保留一些必要的状态信号和控制信号,现确定顶层模块的端口与功能见表2:
表2 顶层模块的端口与功能
子模块设计
本设计的异步FIFO划分为5个子模块:读指针控制模块、写指针控制模块、存储RAW模块、读指针同步到写时钟域模块,写指针同步到读时钟域模块。
依次介绍如下:
两个同步模块
这两个同步模块同前文介绍的一样,是两个寄存器连接的一起。
写指针同步到读时钟域模块代码如下
module syne_w2r(rwptr2,wptr,rclk,rrst_n);
parameter ADDRSIZE=4;
output [ADDRSIZE:0] rwptr2; //同步后的写指针
input [ADDRSIZE:0] wptr; //同步前的写指针
input rclk,rrst_n;
reg [ADDRSIZE:0] rwptr2,rwptr1; //两个中间寄存器
always @(posedge rclk or negedge rrst_n)
if(!rrst_n)
{rwptr2,rwptr1}<=0; //复位
else
{rwptr2,rwptr1}<={rwptr1,wptr}; //寄存器串联
Endmodule
读指针同步到写时钟域模块代码如下:
module sync_r2w(wrptr2,rptr,wclk,wrst_n);
parameter ADDRSIZE=4;
output [ADDRSIZE:0] wrptr2;
input [ADDRSIZE:0] rptr;
input wclk, wrst_n;
reg [ADDRSIZE:0] wrptr2, wrptrl;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
{wrptr2, wrptrl}<=0;
else
{wrptr2,wrptrl}<= {wrptrl,rptr};
endmodule
功能比较简单,故不做仿真验证
存储模块
module fifomem(rdata, wdata, waddr,raddr, wclken, wclk,rclken,rclk); parameter DATASIZE=8; // 数据宽度parameter ADDRSIZE=4; // 地址宽度output [DATASIZE-1:0] rdata;
input [DATASIZE-1:0] wdata;
input wclken, wclk,rclken,rclk; //读写控制和时钟input [ADDRSIZE-1:0] raddr, waddr; //输入读写地址reg [DATASIZE-1:0] rdata;
reg [DATASIZE-1:0] MEM [0:(1<<ADDRSIZE)-1]; //存储体
always @(posedge rclk) //读时钟读出数据
if (rclken) rdata = MEM[raddr];
always @(posedge wclk)
if (wclken) MEM[waddr] <= wdata; //写时钟写入数据
endmodule
此模块构造了一个存储器,按读写始终安排输入和输出。
编写测试模块如下:
module tbmem;
parameter DATASIZE=8;
parameter ADDRSIZE=4;
wire [DATASIZE-1:0] rdata;
reg [DATASIZE-1:0] wdata;
reg wclken, wclk,rclken,rclk;
reg [ADDRSIZE-1:0] raddr, waddr;
integer seed1;
initial
begin
wclk=0;rclk=0;seed1=20; //初始化
waddr=0;raddr=0;
end
always #9 wclk=~wclk; //生成写时钟
always #11 rclk=~rclk; //生成读时钟
always @(posedge wclk)
wdata<={$random(seed1)/256}; //产生随机写入数据
initial
begin
wclken=1;rclken=0;
repeat (10) @(posedge wclk); //写入10个数据
wclken=0;rclken=1;
repeat (6) @(posedge rclk); //读出6个数据
wclken=1;rclken=1;
#99 $stop;
end
always @(posedge wclk)
if(wclken==1)
waddr=waddr+1; //写地址生成
always @(posedge rclk)
if(rclken==1)
raddr=raddr+1; //读地址生成
fifomem fifomem(rdata, wdata, waddr,raddr, wclken, wclk,rclken,rclk);
endmodule
仿真波形如图2所示
图2 存储仿真波形
第一行为读出数据,第二行为写入数据,图中第一行和第二行数据是完全对应的。
读地址控制模块
FIFO存储器空状态是在读时钟域中生成的,这样就可以确保一旦FIFO存储器达到空状态时就能被检测到。
也就是说,在读时钟域里读指针可以在读时钟周期内与同步而来的写指针(包括附加的最高位MSB)进行比较。
当读指针与同步的写指针rwptr2相等时,FIFO存储器为空状态,此时FIFO存储器停止读取数据,否则会导致向下溢出(underflow)。
比较读指针和同步的写指针以生成空标志的比较器很容易实现。
FIFO存储器的指针总是预先指向下一个内存位置,每进行一次读写操作,相应的指针就增加一次。
如果读指针与同步的写指针rwptr2的附加位(这两个指针的最高位MSBs)是相等的,则这两个指针经历了相同的循环次数,假如这时两个指针的低位(共n-1位)也相等,FIFO存储器就为空状态。
在这个模块中包含了除读同步模块之外的所有读时钟域的逻辑电路。
读指针是"两重格雷码计数器”。
位指针rptr被同步到写时钟域中,n-1位指针用于产生地址。
当读指针rptr 的下一个状态rgnext等于同步的写指针rwptr2时,空状态标志将在下一个读时钟的上升沿被置位。
这个模块已经是一个读时钟域的同步时序电路,这有利于进行静态时序分析。
模块包含读指针电路和空标志逻辑电路,代码如下:
module rptr_empty(rempty,raddr,rptr,rwptr2,rinc,rclk,rrst_n);
parameter ADDRSIZE=4;
output rempty;
output [ADDRSIZE-1:0] raddr;
output [ADDRSIZE:0] rptr;
input [ADDRSIZE:0] rwptr2;
input rinc, rclk,rrst_n;
reg [ADDRSIZE:0] rptr,rbin,rgnext,rbnext;
reg rempty,raddrmsb;
always @(posedge rclk or negedge rrst_n)
if(!rrst_n)
begin
rptr <=0;
raddrmsb<=0;
end
else
begin
rptr <=rgnext;
raddrmsb <=rgnext[ADDRSIZE]^rgnext[ADDRSIZE-1];
end
always @(rptr or rinc)
begin:Gray_inc
integer i;
for(i=0;i<=ADDRSIZE;i=i+1)
rbin[i]=^(rptr>>i); //格雷码转换为二进制码
if(!rempty)
rbnext=rbin+rinc; //增加FIFO计数
else
rbnext=rbin;
rgnext = (rbnext>>1)^rbnext; //二进制转化为格雷码
end
always @(posedge rclk or negedge rrst_n)
if(!rrst_n)
rempty<=1'b1; //复位时输出空
else
rempty<=(rgnext==rwptr2); //否则判断是否满足条件
assign raddr = {raddrmsb,rptr[ADDRSIZE-2:0]}; //读地址指针
endmodule
读地址控制模块主要的部分按代码顺序有复位部分,完成复位功能计数部分,完成格雷码转二进制计数再转换成格雷码;空状态判断部分,复位时输出空,读指针和同步后的写指针相同时出示空状态;最后是一个读地址的拼接输出。
为此模块编写测试模块,代码如下:
module tbrptr;
parameter ADDRSIZE=4;
wire rempty;
wire [ADDRSIZE-1:0] raddr;
wire [ADDRSIZE:0] rptr;
reg [ADDRSIZE:0] rwptr2;
reg rinc, rclk,rrst_n;
initial
begin
rclk=0;rrst_n=1;rinc=0;
rwptr2=14;
#3 rrst_n=0;
#4 rrst_n=1;
#6 rinc=1;
end
always #11 rclk=~rclk;
initial
begin
repeat (5) @(posedge rclk);
@(posedge rempty);
#20 $stop;
end
rptr_empty rptr_empty(rempty,raddr,rptr,rwptr2,rinc,rclk,rrst_n);
endmodule
仿真波形如图3所示
图3 读地址控制模块
波形中第一行是空状态,一开始复位一次,计数到0110时与预置的写指针相同,空状态重新变为高电平。
此模块可正常完成地址计数和空状态的生成,功能验证结果正确。
写指针控制模块
与读指针控制模块代码基本相同。
此模块仅在满状态判断部分不相同。
代码如下:
module wptr_full(wfull,waddr,wptr,wrptr2,winc,wclk,wrst_n);
parameter ADDRSIZE = 4;
output wfull;
output [ADDRSIZE-1:0] waddr;
output [ADDRSIZE:0] wptr;
input [ADDRSIZE:0] wrptr2;
input winc, wclk, wrst_n;
reg [ADDRSIZE:0] wptr, wbin, wgnext, wbnext;
reg wfull, waddrmsb;
wire w_2ndmsb,wr_2ndmsb; //添加的判断信号
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
begin
wptr <= 0;
waddrmsb <= 0;
end
else
begin
wptr <=wgnext;
waddrmsb <=wgnext[ADDRSIZE]^wgnext[ADDRSIZE-1];
end
always @(wptr or winc)
begin: Gray_inc
integer i;
for(i=0; i<=ADDRSIZE; i=i+1)
wbin[i]= ^ (wptr>>i); //格雷码转二进制
if (!wfull)
wbnext = wbin+winc; //FIFO计数
else
wbnext = wbin;
wgnext=(wbnext>>1) ^ wbnext; //二进制转格雷码
end
assign w_2ndmsb = wgnext[ADDRSIZE] ^ wgnext[ADDRSIZE-1]; //写指针前两位异或
assign wr_2ndmsb = wrptr2[ADDRSIZE] ^ wrptr2[ADDRSIZE-1]; //同步后的读指针前两位异或
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
wfull<=0;
else
wfull <= ((wgnext[ADDRSIZE] !==wrptr2[ADDRSIZE])&&(w_2ndmsb== wr_2ndmsb)
&& (wgnext[ADDRSIZE-2:0]== wrptr2[ADDRSIZE-2:0])); //三个判断条件均满足则满
assign waddr= {waddrmsb,wptr[ADDRSIZE-2:0]};
endmodule
由于功能基本相似,此模块不再列出仿真
整体仿真结果
将五个子模块合成一个顶层模块,代码如下:
module fifo_asyn1(rdata,wfull,rempty,wdata,winc,wclk,wrst_n,rinc,rclk,rrst_n);
parameter DSIZE = 8; //数据存储宽度
parameter ASIZE = 4; //存储地址宽度
output [7:0] rdata;
output wfull;
output rempty;
input [7:0] wdata;
input winc,wclk,wrst_n;
input rinc,rclk,rrst_n;
wire [3:0] waddr,raddr;
wire [4:0] wptr,rptr,wrptr2,rwptr2;
sync_r2w sync_r2w(.wrptr2(wrptr2),.rptr(rptr),.wclk(wclk),.wrst_n(wrst_n)); syne_w2r syne_w2r(.rwptr2(rwptr2),.wptr(wptr),.rclk(rclk),.rrst_n(rrst_n)); fifomem #(DSIZE,ASIZE) fifomem(.rdata(rdata),.wdata(wdata),.waddr(waddr),.raddr(raddr),
.wclken(winc),.wclk(wclk),.rclken(rinc),.rclk(rclk));
rptr_empty #(ASIZE) rptr_empty(.rempty(rempty),.raddr(raddr),.rptr(rptr),.rwptr2(rwptr2),
.rinc(rinc),.rclk(rclk),.rrst_n(rrst_n));
wptr_full #(ASIZE) wptr_full(.wfull(wfull),.waddr(waddr),.wptr(wptr),.wrptr2(wrptr2),
.winc(winc),.wclk(wclk),.wrst_n(wrst_n));
Endmodule
为此模块编写测试模块,代码如下:
module tbfifo;
parameter DSIZE = 8;
parameter ASIZE = 4;
wire [7:0] rdata;
wire wfull;
wire rempty;
reg [7:0] wdata;
reg winc,wclk,wrst_n;
reg rinc,rclk,rrst_n;
integer seed;
initial
begin
wclk=0;rclk=0;seed=11;
winc=0;rinc=0;
wrst_n=1;rrst_n=1;
#2 wrst_n=0;rrst_n=0;
#3 wrst_n=1;rrst_n=1;
end
always #9 wclk=~wclk;
always #11 rclk=~rclk;
always @(posedge wclk)
wdata<={$random(seed)/256};
initial
begin
#20 winc=1;
repeat (12)@(posedge wclk);
winc=0;rinc=1;
repeat (6)@(posedge rclk);
winc=1;rinc=1;
@(posedge wfull);
winc=0;rinc=1;
@(posedge rempty);
#20 $stop;
end
fifo_asyn1 myasynfifo(rdata,wfull,rempty,wdata,winc,wclk,wrst_n,rinc,rclk,rrst_n); endmodule
仿真波形如图4所示
这是整体效果,从一开始复位信号产生空状态,然后写入一些数据到FIFO之中,再边写边读,但是由于写周期快,所以FIFO之中的数据量逐渐增加,直至出现写满状态。
接下来进入只读阶段,连续读出FIFO的数据,直到读空为止,这样所以得工作状态都得到了验证。
图4 仿真波形图
图5是波形最后的部分放大
图5 数值验证
此图中来查看数据准确性:第一行是输出的数据,第四行是输入的数据,在wfull信号出现高电平之后进入只读状态,此时读出的数据89与图中第四行第三个数相等,然后按顺序输出刚写入的数据,数值部分验证正确。
该电路图得到的模块图如图6
图6 整体模块结构图
心得体会
在本次实验中,应用了两级同步器,格雷码,存储器等知识;以及亚稳态的处理,格雷码在跨时钟域方面的应用,顶层模块的编写等方法,让本人学到很多。