本章节主要参考《SOC设计方法与实现 第三版》第七章 。也是整个SOC架构学习的起点,下面我们正式开始!
目前流行的设计架构:GALS(Global Asynchronize Local Synchronize),即全局异步局部同步,这是顺应了多核SOC设计的潮流同时也符合EDA工具对同步电路设计的广泛支持。
同步电路的定义:触发器、寄存器都由一个统一的时钟控制。并且在同步电路中,为方便后端设计,一般使用统一触发方式(上升沿或下降沿中的一种)
同步电路时序收敛:满足触发器的建立时间和保持时间。EDA的时序分析工具可以检查同步电路的收敛问题。
同步电路优缺点:
同步性减少了电路竞争冒险和毛刺噪声
时钟偏斜(时钟到达每个触发器时间不一致,可以用EDA时钟树综合,以最长的时钟路径为基准,短的路径加入延时单元)和功耗问题
异步电路的触发器可能在任何时间跳变。
异步电路采用握手协议实现自同步。常用的握手协议分为二相位和四相位握手。
下图的Req和Ack是简单的握手信号,即request和acknowledge,先握手的是request而后回应的是acknowledge
二相位握手协议在Req和Ack的任何一对跳变沿组合处均可实现一次数据传输。而四相位握手协议只能实现一次数据传输。四相位握手协议是首选(因为稳定性高)
异步电路优缺点:
模块化突出、对信号延迟不敏感、没有时钟偏斜、高速低功耗
设计复杂,缺乏EDA工具支持
亚稳态本质上是信号跨时钟域传输时,输入的信号可能会不满足某个触发器的建立保持时间要求,从而锁存进一个不正常的电平(可能代表着NMOS和PMOS同时导通,并且可能传播下去,会造成后续电路工作不正常)
降低亚稳态发生概率:使用二级触发器(同步器),其本质是因为亚稳态最终会稳定在一个电平上,增加一级D触发器可以保证等亚稳态恢复到0或者1再采样,这样亚稳态就不会再传播下去。
一点思考:(如有不对请指正)
但是这样不能保证跨时钟域的数据传输正确,只能保证亚稳态概率降到很低。
因为发生亚稳态时,第二级触发器等待第一级触发器亚稳态输出回恢复稳定再采样,然而恢复到0还是1这是不能确定的,因此在这种情况下二级触发器采样输出的数据虽说不会有暂稳态传播下去,但是数据有可能是错误的
同步器的代码如下(参考上图)
module synchronizer(
input bclk,
input reset_b,
input adat,
output bdat
);
reg bdat1,bdat2;
always@(posedge bclk, negedge reset_b)begin
if(reset_b)
{bdat2,bdat1}<=2'b0;
else
{bdat2,bdat1}<={bdat1,adat};
end
assign bdat=bdat2;
endmodule
同步的定义:把异步信号采样到目的时钟域的动作
1.慢时钟域控制信号同步到快时钟域。
问题描述:直接用同步器采样的话,慢时钟域持续1个慢时钟周期的控制信号采样到快时钟域可能会持续多个快周期。在慢时钟域中被认为是一次控制就会在快时钟域中被认为多次控制操作,该问题如下图所示。
经过快时钟下的同步器采样(两级触发器),得到rd_en_s2f2
控制信号(图上标注有误,rd_en_s2f
应当为rd_en_s2f2
),该信号在快时钟域下持续多个周期而不是单个周期,需要对其进行处理,将其转化为快时钟域下单个周期的信号,需要缩短控制信号。
处理方法:采用简单的逻辑波形变换(注意,因为rd_en_s2f1
可能有亚稳态现象,因此只能用同步器输出的信号进行变换(即rd_en_s2f2
)),具体变换如下图所示。
代码如下
module synchronizer(
input clk_fst,
reset_b,
rd_en,
output rd_en_s2f
);
reg rd_en_s2f1,
rd_en_s2f2,
rd_en_s2f3;
always@(posedge clk_fst or negdege reset_b)begin//多级触发器赋值
if(~reset_b)
{rd_en_s2f3,rd_en_s2f2,ed_en_s2f1}<=3'b000;
else
{rd_en_s2f3,rd_en_s2f2,ed_en_s2f1}<={rd_en_s2f2,rd_en_s2f1,rd_en};
end
always@(rd_en_s2f3,rd_en_s2f2)
case({rd_en_s2f3,rd_en_s2f2})
2'b01://条件句
rd_en_s2f<=1'b1;
default:
rd_en_s2f<=1'b0;
endcase
endmodule
重要提示:学习多级触发器的verilog语句,熟练使用{ }语法
小结:慢时钟域控制信号同步到快时钟域的本质就是一个边缘检测或者信号计数器。
不管持续时间多长的信号(只要大于一个快时钟周期),都会生成一个快时钟域内持续一个周期的信号。
通过对上述代码中的条件句进行控制,还可以实现下降沿的检测
★★★实例练习:按键控灯
问题转化:翻转与按下的时间,即高电平的时间无关,只与按下这个动作有关系。因此,该问题可以转换为上升沿检测器
思考与回答:
Q1:既然是上升沿检测,能否用@(posedge button)
来直接检测上升沿?即如下图所示,将按钮输入电平直接接在触发器时钟端?
module onoff (button,light); input button; output light; reg light; always@posdege button) begin light <= ~light; end endmodule
A1:不可以,这样不规范,电路中时钟端就是接时钟的,不能将输入接到时钟端。
-------------------------------------------------------------------------------------------------------------
Q2:button
作为外界的异步输入,与驱动电路的时钟并不一致,能直接接入触发器输入吗?
A2:不能,异步信号需要先使用同步器完成时钟域同步,来避免亚稳态现象。
-------------------------------------------------------------------------------------------------------------
Q3:同步完之后如何实现上升沿检测?
A3:输入是慢时钟域信号(因为是人手,比电路时钟变化更慢),因此就是慢时钟域信号在快时钟域下的上升沿检测问题,可以使用上述波形变换的方法(比较简单),也可以使用状态机的方法来实现。
实现代码如下
波形变换法:
button_light.v
module button_light
(
input clk ,
button , //i_pulse
rst_n ,
output reg light //o_pulse
);
reg button_d1 ,
button_d2 ,
button_d3 ; //button_d3用于波形变换
wire pulse ;
/*同步器,用于同步输入,避免亚稳态*/
always @(posedge clk or negedge rst_n) begin
if(~rst_n)
{button_d3,button_d2,button_d1}<=2'b00;
else
{button_d3,button_d2,button_d1}<={button_d2,button_d1,button};
end
/*波形变换法实现上升沿检测*/
assign pulse=(~button_d3)&(button_d2);
//light根据pulse翻转逻辑
always @(posedge clk or negedge rst_n) begin
if(~rst_n)
light<=0;
else
if(pulse)
light<=~light;
end
endmodule
button_light_tb.v
`timescale 1ns/1ps
`include "button_light.v"
module button_light_tb;
reg rst_n ,
clk ,
button ;
wire light ;
//模块例化
button_light b1(
clk ,
button ,
rst_n ,
light
);
//激励
initial begin
$dumpfile("button_light.vcd");
$dumpvars;
clk<=0;
rst_n<=0;
button<=0;
#15 rst_n<=1;
#20 button<=1;
#160 button<=0;
#125 button<=1;
#130 button<=0;
#55 button<=1;
#60 button<=0;
#1000 $finish;
end
//时钟生成
always #10
clk=~clk;
endmodule
最终结果如下
状态机法
先画状态图,有点丑,大概理解一下(╥﹏╥),懒得重新画了,反正也不难
button_light2.v
//按键控灯练习,见https://blog.csdn.net/SuperiorEE/article/details/128880282
//波形变换法,使用moore机
module button_light2#(
parameter S1=3'b001, //one-hot encode state
S2=3'b010,
S3=3'b100
)
(
input clk ,
button ,
rst_n ,
output reg light
);
reg [2:0] current_state ,
next_state ;
reg button_d1 ,
button_d2 ;
/*同步器,用于同步输入,避免亚稳态*/
always @(posedge clk or negedge rst_n) begin
if(~rst_n)
{button_d2,button_d1}<=2'b00;
else
{button_d2,button_d1}<={button_d1,button};
end
//第一段:现态次态切换
always @(posedge clk,negedge rst_n) begin
if(~rst_n)
current_state<=S1; //状态复位
else
current_state<=next_state; //状态转换
end
//第二段:次态组合逻辑
always@(*)begin
case (current_state)
S1:
next_state=(button_d2==0)?S1:S2;
S2:
next_state=(button_d2==0)?S1:S3;
S3:
next_state=(button_d2==0)?S1:S3;
default:
next_state=S1;
endcase
end
//第三段:输出电路(可以是组合或者时序,根据mealy或者moore确定,时序电路可以避免输出随输入变化的毛刺,但是会比mealy慢一个时钟)
always@(posedge clk,negedge rst_n)begin
if(~rst_n)
light<=0;
else if(current_state==S2)
light<=~light;
end
endmodule
testbench
内容大致不变,只需要改变include
即可
对比发现,两种方法输出大致相同,只是实现手段不同。
实际上波形变换法快一个周期,因为其脉冲输出pause更快,前沿可以与同步器输出对齐。而状态机法,由于是moore机,其输出会慢一个时钟周期。
其核心都是慢时钟域的控制信号同步到快时钟域下(即电平变脉冲、上升沿检测)。但是从代码上来看,波形变换法更加简洁。
本题总结:核心思想就是把问题转换为上升沿检测电路,同时注意对异步输入需要加入同步器。
2.快时钟域控制信号同步到慢时钟域。
问题描述:慢时钟域同步快时钟域下持续一个快周期的信号,在慢时钟域中会不满一个慢时钟周期,因此在下一个慢时钟到来之前,信号就已经无效。该问题如下图所示。
因此需要延长控制信号。
处理方法1:加入握手机制,在快时钟域中加入反馈,只有接收方确认收到有效控制信号后,控制信号再失效。
代码如下:
/*快时钟域控制信号同步到慢时钟域*/
module f2s_control(
input adat,
rst,
aclk,
bclk,
output reg bdat
);
reg adat1,
bdat1, bdat2, bdat3,
abdat1, abdat2;
//实例化慢时钟域同步器
always @(posedge bclk or negedge rst) begin
if(~rst)
{bdat2,bdat1}<=2'b00;
else
{bdat2,bdat1}<={bdat1,adat1};
end
//实例化快时钟域同步器
always @(posedge aclk or negedge rst) begin
if(~rst)
{abdat2,abdat1}<=2'b00;
else
{abdat2,abdat1}<={abdat1,bdat2};
end
//实例化快时钟域同步慢时钟域方法:缩短
always @(posedge bclk or negedge rst) begin
if(~rst)
bdat3<=1'b0;
else
bdat3<=bdat2;
end
always @(bdat3,bdat2) begin
bdat<=({bdat3,bdat2}==2'b01)?1'b1:1'b0;
end
//实例化慢时钟域同步快时钟域方法:反馈延时
always @(posedge aclk or negedge rst) begin
if(~rst)
adat1<=1'b0;
else //adat触发,abdat2关闭
if(abdat2)
adat1<=1'b0;
else
if(adat)
adat1<=1'b1;
end
endmodule
testbench
代码如下:
/*快时钟域控制信号同步到慢时钟域*/
/*测试*/
`timescale 1ns/1ns
`include "f2s_control.v"
module f2s_control_tb;
//************<端口声明>****************
reg adat,
rst,
aclk,
bclk;
wire bdat;
//***********<模块例化>******************
f2s_control M1(
adat,
rst,
aclk,
bclk,
bdat
);
initial begin
$dumpfile("f2s_control.vcd");
$dumpvars;
aclk<=0;
bclk<=0;
rst<=0;
adat<=0;
#12 rst<=1;
#20 @(posedge aclk) adat<=1;
@(posedge aclk) adat<=0;
#1000 $finish;
end
always #7
aclk<=~aclk;
always #10
bclk<=~bclk;
endmodule
存在的局限:两次控制信号之间的间隔不能太小,要让相关握手信号先恢复。
相关练习:牛客网verilog VL49
处理方法2【停时钟法】:当检测到控制信号输入时,通过停快时钟域的时钟来延长快时钟域控制信号,当收到慢时钟域握手反馈后,再恢复时钟。
代码如下:
//方法2:停时钟法
module f2s_control(
input adat,
rst,
aclk,
bclk,
output reg bdat
);
reg adat1,
bdat1,
bdat2,
bdat3,
abdat1,
abdat2,
stall;
wire aclk_gt;
//双向同步器
always @(posedge aclk or negedge rst) begin
if(~rst)
{abdat2,abdat1}<=2'b00;
else
{abdat2,abdat1}<={abdat1,bdat2};
end
always @(posedge bclk or negedge rst) begin
if(~rst)
{bdat2,bdat1}<=2'b00;
else
{bdat2,bdat1}<={bdat1,adat1};
end
//慢同步到快波形变化
always @(posedge bclk or negedge rst) begin
if(~rst)
bdat3<=2'b00;
else
bdat3<=bdat2;
end
always @(*) begin
bdat<=({bdat3,bdat2}==2'b01)?1:0;
end
//反馈组合逻辑
assign aclk_gt=aclk & stall;
always@(*)begin
if(abdat2==1)
stall<=1'b1;//重启时钟
else if(adat1==1)
stall<=1'b0;//停时钟
else
stall<=1'b1;
end
always @(posedge aclk_gt or negedge rst) begin
if(~rst)
adat1<=1'b0;
else
adat1<=adat;
end
endmodule
存在的局限:与方法一一样,两次控制信号之间的间隔不能太小,需要快时钟重启让上一次的控制信号先恢复到无效(即低电平)。
使用先入先出队列,即FIFO.
它由双端口存储器和一组控制逻辑组成
双端口存储器:一个端口用来写存储器,另一个端口用来读存储器,读和写可以同时进行,读写时钟可以完全不同。
双端口存储器的Verilog代码如下,主要注意学习寄存器数组的使用
`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
FIFO行为简述与结构推导:
如前所述,由于FIFO内部是有双端口存储器,因此读写操作是相互独立的)
写操作:
待同步的数据在写信号的控制下,只要不FULL(即写满),就会将数据写入到FIFO中。
每成功写入一个数据,写指针的地址就会+1
读操作:
目标时钟域只要不EMPTY(即读空),就执行读操作,读出之前写入的数据。
每成功读出一个数据,读指针的地址就会+1
其大致行为可以类比数据结构中的循环队列
请思考:
两个问题:
1.控制逻辑怎么产生产生读空和写满信号?谁来产生?交换一下会有问题吗?
2.如何解决地址同步问题,为什么不能直接使用比较器?
结合以上思考,控制逻辑主要是负责产生EMPTY(读空) 和 FULL(写满) 信号。而这两个信号主要是通过 写指针地址wptr 和 读指针地址rptr 进行相互比较产生的。
但是,为什么不能直接使用比较器这种组合逻辑电路来比较读写地址并生成读空写满信号呢?
回答:以EMPTY信号为例,由于读指针地址(读时钟域)和写指针地址(写时钟域)是两个不同时钟域的异步信号。
如果EMPTY是由读写地址指针的组合逻辑产生的,那么EMPTY就会在读时钟和写时钟的边沿都有可能发生变化。因此,EMPTY既是读地址也是写地址的异步信号。
因此在读时钟域或者写时钟域内,由于EMPTY是异步信号,就可能不满足建立保持时间,会产生亚稳态问题。
解决策略【异步地址同步】:先将一个地址同步到另一个地址的时钟域,在两个地址都在同一时钟域下时,再生成EMPTY或者FULL信号,这样这些读空写满信号就成了同步信号。
仍然以EMPTY信号为例,可以将写地址指针通过同步器(2级D触发器打两拍)同步到读时钟域,然后在读时钟域内根据同步过来的写指针地址和原来的读指针地址生成读空信号EMPTY。
但是,对于EMPTY信号的生成,为什么要把写地址同步到读地址时钟域,能否换成 把读地址同步到写地址?
回答:我们从读空信号EMPTY的本质出发,如果读写地址都是实时产生的,那么读地址等于写地址时(即读地址追上了写地址)就代表读空了。但是现在有一个信号是同步过来的,同步过来的信号比原时钟域的信号会有滞后。
假设我们把读地址同步到写地址时钟域,这说明读地址是滞后的,因此在写地址时钟域下,读写地址相同产生EMPTY时,真实情况是,真实读地址超过了同步过来的滞后读地址,这样读地址就有很大的风险超过写地址,这是不允许的,相当于读指针溢出,会读出错误数据。
而反过来,写地址同步到读时钟域,即同步写地址滞后于真实写地址。因此当读写地址相同产生EMPTY时,真实情况是,真实读地址等于滞后的写地址,而可能落后于真实的写地址。这种情况是允许的,因为并没有读空,只是会短暂停歇一会(等几拍)。
【小结】经过上述推理,我们可以简记为,读空信号EMPTY在读时钟域中生成,而写满信号FULL在写时钟域中生成。
这里我们还忽略了一个比较重要的问题:读空写满时都是
读指针地址 = 写指针地址 读指针地址=写指针地址 读指针地址=写指针地址
应该如何区分这两种情况呢?
解决策略【扩展地址最高位】:
以8个存储单元的存储器为例,其物理地址为3bit,从000-111,每个地址表征一个物理意义上的存储单元。
我们现在扩展其最高位地址,将其变为4bit,即0000-1111,低3位仍然表征物理意义的存储单元,而扩展出来的第四位用来区分FULL和EMPTY的情况,这一位用来区分是否差了一轮。
首先,有一个固有约束,那就是读地址不允许在物理意义上超过写地址,这代表了还没写就读了,这显然是不允许的。而我们在前述的小结中所规定的地址同步方法就自动避免了这种情况的出现。
因此,在低3位相同的情况下,如果最高位相同,则代表读的数据和写的数据相同,这是EMPTY读空的情况,如果最高位不同,则代表读写数据之间差了一轮,并且由于读地址限制不能超过写地址,因此只能是写地址超过了读地址一轮,这是FULL写满的情况。
时刻牢记,读指针和写指针都是读写完成后再加1,因此所指的都是将要读/写的单元。
【小结】:
EMPTY是读地址 追上 写地址。即地址低三位相同,最高位也相同。
FULL是写地址 套圈追上 读地址。即地址低三位相同,最高位不同。
前述使用同步器方法来同步地址生成EMPTY和FULL信号,避免亚稳态。在此基础上,使用格雷码编码可以进一步减少毛刺和亚稳态现象,因为格雷码每次加一只改变一位。我们仅需在原来地址自增二进制计数器的基础上增加一个二进制转格雷码的组合逻辑电路即可。
格雷码计数器:《SOC设计方法与实现 第三版》136页给出了设计代码。但是这里与书中代码不太相同,因为发现书中代码有些冗余了。这里的实现思路是——
先用时序电路always块写出一个二进制计数器,然后再用组合逻辑电路实现二进制码到格雷码的转换。
Verilog代码如下
//2023年2月20日19:14:55
//格雷码计数器,由二进制计时器和二进制转换格雷码组成
module gray_inc
#(
parameter SIZE=4, //位宽
parameter INC=1 //增量
)
(
input wire clk,
rst_n,
output reg [SIZE-1:0] gray
);
reg [SIZE-1:0] bin;
always @(posedge clk or negedge rst_n) begin//二进制计数器
if(~rst_n)
bin<=0;
else
bin<=bin+INC;
end
always @(*) begin
gray<=(bin>>1)^bin;
end
endmodule
Testbench如下
`timescale 1ns/100ps
`include "gray_inc.v"
module gray_inc_tb;
parameter SIZE=5,
INC=1;
reg clk,
rst_n;
wire [SIZE-1:0] gray;
//*********<模块例化>*************
gray_inc
#(
SIZE,
INC
)
g1 //注意g1的位置在参数后,在port前
(
clk,
rst_n,
gray
);
initial begin
$dumpfile("gray_inc.vcd");
$dumpvars;
rst_n<=0;
clk<=0;
#25 rst_n<=1;
#1000 $finish;
end
always #10
clk<=~clk;
endmodule
重点提示: 二进制和格雷码互转的公式
二进制转格雷:
gray = (bin >> 1) ^ bin
格雷转二进制(解码):
for(i=0;ibin[i]=^(gray>>i)
【易错点】:引入格雷码之后,需要注意格雷码判断相等或低位相等的准则与二进制的区别
举个例子:
二进制 | 格雷码 |
---|---|
000 | 000 |
001 | 001 |
010 | 011 |
011 | 010 |
100 | 110 |
101 | 111 |
110 | 101 |
111 | 100 |
例如判断写满时,二进制判断方法是最高位相反,其余低位相同。而对于格雷码不是这样。格雷码011和111低位相同,二进制分别为010和101,对应的二进制低位不同。简而言之就是格雷码判断部分位相同的规则与二进制码不能对应起来。
【重要提示】格雷码制下判断读空与写满
读空empty:每一位都完全相同才判断为读空
写满full:写时钟域的格雷码wgray_next 和 被同步到写时钟域的读指针wr2_rp 高两位不相同,其余各位完全相同
代码表述如下:assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同 assign empty = ( rd_addr_gray == wr_addr_gray_d2 );
这里特别感谢这篇博客给我的启发让我避免了把格雷码再转化回二进制的复杂操作。
综合以上部分,异步FIFO的框图如下图所示
异步FIFO完整代码如下
//牛客网VL45 异步FIFO
//2023年2月27日15:45:43
//本题格雷码不用打一拍,可以直接由组合逻辑生成,只是因为牛客网为了过测试需要打一拍
`timescale 1ns/1ns
//!!!问题:格雷码低位相同其二进制码低位不一定相同
/*
000 000
001 001
010 011
011 010
100 110
101 111
110 101
111 100
格雷码011和111低位相同,二进制分别为010和101,低位不同
因此需要注意empty和full的判断条件不同
*/
/***************************************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
);
//扩展地址
reg [$clog2(DEPTH):0] rptr_expand ,
rptr_expand_g,
rptr_expand_g_d1,
rptr_expand_g_d2,//写时钟域的读地址
wptr_expand ,
wptr_expand_g,
wptr_expand_g_d1,
wptr_expand_g_d2;//读时钟域的写地址
wire wenc,
renc;
assign wenc=winc&(~wfull); //写使能且未写满
assign renc=rinc&(~rempty); //读使能且未读空
/**************原始指针读写变化逻辑***********************/
always@(posedge wclk,negedge wrstn)begin
if(~wrstn)
wptr_expand<=0;
else if(wenc)
wptr_expand<=wptr_expand+1;
else
wptr_expand<=wptr_expand;
end
always@(posedge rclk,negedge rrstn)begin
if(~rrstn)
rptr_expand<=0;
else if(renc)
rptr_expand<=rptr_expand+1;
else
rptr_expand<=rptr_expand;
end
/*********指针地址转换为格雷码*******************/
//由于定义时格雷码是reg类型,因此打一拍更加规范,当然也为了过本题的原因
always@(posedge wclk,negedge wrstn)begin
if(~wrstn)
wptr_expand_g<=0;
else
wptr_expand_g<=(wptr_expand>>1)^(wptr_expand);
end
always@(posedge rclk,negedge rrstn)begin
if(~rrstn)
rptr_expand_g<=0;
else
rptr_expand_g<=(rptr_expand>>1)^(rptr_expand);
end
/******************同步器************************/
always @(posedge wclk,negedge wrstn) begin
if(~wrstn)
{rptr_expand_g_d2,rptr_expand_g_d1}<=2'b0;
else
{rptr_expand_g_d2,rptr_expand_g_d1}<={rptr_expand_g_d1,rptr_expand_g};
end
always @(posedge rclk,negedge rrstn) begin
if(~rrstn)
{wptr_expand_g_d2,wptr_expand_g_d1}<=2'b0;
else
{wptr_expand_g_d2,wptr_expand_g_d1}<={wptr_expand_g_d1,wptr_expand_g};
end
/***************EMPTY&FULL************************/
assign rempty=(rptr_expand_g==wptr_expand_g_d2)?1:0;
assign wfull=(wptr_expand_g=={~(rptr_expand_g_d2[$clog2(DEPTH)-:2]),rptr_expand_g_d2[$clog2(DEPTH)-2:0]})?1:0;
/********************双口RAM例化****************/
dual_port_RAM
#(
DEPTH,
WIDTH
)
d1
(
wclk //
,wenc //
,wptr_expand[$clog2(DEPTH)-1:0] //
,wdata //
,rclk //
,renc //
,rptr_expand[$clog2(DEPTH)-1:0] //!!!
,rdata //
);
endmodule
FIFO的优势:可以有效地解决两个没有任何关系的时钟源之间数据同步的问题
相对于前述异步控制信号的同步,FIFO在架构上可以实现一个数据存放在一个存储单元,并等待读取,从架构上解决了异步信号不同时钟域下的信号缩短和延长问题。
一些思考: 理论上来说,FIFO也可以实现异步控制信号的同步,只需要一个存储单元即可。但是FIFO的引入会加大芯片的电路面积,所以常用于异步数据信号的同步
★★★★;异步复位,同步释放
参考这篇
因为复位信号也需要与时钟满足类似建立时间和保持时间(被称为恢复时间和去除时间)。
由于复位信号在复位开始和复位撤销时,复位信号都会产生变化,都是时钟信号的异步信号,都会产生亚稳态现象。但是,由于复位信号持续时间比较长,因此复位信号开始时如果复位失败,可以在多个时钟周期后可以消除。而复位消除时间需要进行同步,避免亚稳态。这就是异步复位同步释放。
框图如下所示
至此,跨时钟域的信号与数据同步问题便告一段落了,本章学习落下帷幕,熟练掌握异步FIFO,并时刻在脑海中要有异步信号先同步来避免亚稳态的思想。这是我个人的一些总结。
有空再去牛客刷一下跨时钟域传输的题目,相信会有巩固和对异步更新的理解!
END