复位
复位是IC/FPGA设计中常见的操作,通过复位,我们可以使电路中某些寄存器的值为我们指定的一个初始值,从而使得电路能够正常工作
为什么需要复位?
数字电路中的寄存器和RAM上电后的默认状态和数据是不确定的,复位就是将其回到初始状态0,RAM也可由此初始化到0,也可根据设计需要复位到1,此外,当逻辑进入到错误的状态时,用复位将所有逻辑复位到初始状态,如没有复位,则将一直处于错误逻辑状态。
FPGA板上面没有复位按键怎么办?
FPGA内部会有上电复位的功能,不管是ALTERA还是 Xlinx 的 FPGA,都是有上电复位电路,也就是 POR (Power 0n Reset) 电路。FPGA 芯片内部有一个上电检测模块,一旦检测到电源电压超过检测门限后,就产生个上电复位脉冲(Power On Reset)送给所有的寄存器,这个脉冲会自动作用在各个寄存器的复位端,和功能复位管脚共同控制寄存器的复位。
还有一种情况,就是 FPGA 重新配置之后,也会触发上电复位。因此 FPGA 板上面即使没有复位按键也是没有关系的.
复位只有通过按键复位一个控制方式吗?
复位按键是一种控制方式,还有一种上电自动复位控制,如下图所示。
这种是一种简单的 RC 复位电路,电源接通时候,通过 R1 对 C2 进行充电,经过一段后加到电路当中产生复位信号,这个复位信号的上升速度低于电源的上电速度,当复位引脚检测到高电平时候,系统复位结束,进入正常工作状态。
同步复位
复位信号只有在时钟沿来时,才能有效,否则无效
always@(posedge clk)begin
if(~rst_n)
q<=0;
else
q<=d;
end
优点:
1.有利于仿真
2.整个电路都是同步电路,时序分析较为简单,综合得到的时钟频率也较高
3.有利于消除毛刺信号
缺点:
1.复位信号的有效时长必须大于时钟周期,才能真正的被系统识别并完成复位任务。
2.由于大多数的逻辑器件的目标库内的DFF都只有异步复位端口,所以,倘若采用同步复位的话,综合器就会在寄存器的数据输入端口插入组合逻辑,耗费较多的逻辑资源。
异步复位
无论时钟沿是否到来,只要复位信号有效,就对系统进行复位。
always@(posedge clk,negedge rst_n)begin
if(!rst_n)
q<=0;
else
q<=d;
end
优点:
1.大多数的逻辑器件的目标库内的DFF都有异步复位端口,可以节省资源
2.设计相对简单
3.异步复位信号识别方便,而且可以很方便的使用FPGA的全局复位端口
缺点:
1.在复位信号释放(reset)的时候容易出现问题。具体来说:倘若复位释放时恰恰在时钟有效沿附近,就很容易使寄存器输出出现亚稳态,从而导致亚稳态。
最大的问题在于它属于异步逻辑,问题出现在复位释放时,而不是有效时,如果复位释放接近时钟有效沿,则触发器的输出可能进入亚稳态(此时 ck 检测到的 rst n 的状态就会是一个亚态,即是 0 是 是不确定的),从而导致复位失败。
异步复位信号需要满足recovery time(恢复时间)和 removal time(去除时间),才能有效的复位和释放复位,防止出现亚稳态.
- 释放复位时,复位信号在时钟有效沿来临之前就需要提前一段时间恢复到非复位状态,这段时间为 ecverv tme。类似于同步时钟下触发器的setup time.
- 复位时,复位信号在时钟有效沿来临之后,还需要在一段时间内保持不变,这段时间为emoval time。类似于同步时钟下发器的 hold tme.
2.复位信号容易受到毛刺的影响。
在大多数情况下,推荐使用异步复位,同步释放的方式。
异步复位,同步释放
问题例子
always @ (posedge clk or negedge rst_n)
if(!rst_n)
b <= 1'b0;
else
b <= a;
end
//
always @ (posedge clk or negedge rst_n)
if(!rst_n)
c <= 1'b0;
else
c <= b;
end
正常情况下,clk的上升沿c更新为b,b更新为a。一旦进入复位,b,c都清零;但是我们不能确定复位信号rst_n会在什么时候结束。如果结束于b、c寄存器的{launch edge –setup,launch edge+hold}时间之外,那么一切都会正常。但如果恰恰相反,**rst_n的上升沿出现在了clk上升沿的建立和保持时间区间内,此时clk检测到的rst_n的状态就会是一个亚稳态(是0是1不确定)。如果此时认为rst_n为0,那么b、c依然保持复位清零,而如果认为rst_n为1,那么就跳出复位。因为此时rst_n的不确定性,就可能出现4种情况,即b和c都复位或者都跳出复位,再或者一个复位一个跳出复位。**那么后者就会造成一个系统工作不同步的问题,在这个简单的两级异步复位实例中这种危害表现的并不明显,但是我们试想一个大的工程项目里众多的寄存器出现如此情况又会是如何一番景象呢?为了解决这个问题,一个常见的方法就是采用异步复位,同步释放的机制。
异步复位
当rst_async_n有效时,第一个D触发器的输出是低电平,第二个D触发器的输出rst_sync_n也是低电平,方框2中的异步复位端口有效,输出被复位。
同步释放
假设rst_async_n撤除时发生在clk上升沿,如果不加此电路则可能发生亚稳态事件。加上此电路以后,假设第一级D触发器clk上升沿时rst_async_n正好撤除,则D触发器1可能输出高电平,也可能输出亚稳态,也可能输出低电平。但此时第二级触发器不会立刻变高,它要么复位为0,要么跟随前一级触发器的输出Q1,而前一级触发器的输出Q1为0,因此,无论如何第二级触发器的输出都为0,而触发器1的输出,若稳定后Q1为1,那么rst_sync_n在下一个周期被拉高,实现同步释放;如果稳定后的Q1为0,那么下个周期rst_sync_n仍为0,但此时由于rst_async_n已经为高,所以Q1在下一个周期必为1,那么rst_sync_n在下一个时钟上升沿到来时被拉高,也实现了同步释放,只是晚了一个周期而已。
实际案例
module dff_sync(
input clk,
input rst_async_n,
output rst_sync_n
);
reg rst_s1;
reg rst_s2;
always @ (posedge clk, negedge rst_async_n)
if (!rst_async_n) begin
rst_s1 <= 1'b0;
rst_s2 <= 1'b0;
end
else begin
rst_s1 <= 1'b1;
rst_s2 <= rst_s1;
end
assign rst_sync_n = rst_s2;
endmodule
RTL视图
实际例子:
module dff_sync(
input clk,
input rst_async_n,
input a,
output rst_sync_n,
output reg b
);
reg rst_s1;
reg rst_s2;
always @ (posedge clk, negedge rst_async_n)
if (!rst_async_n) begin
rst_s1 <= 1'b0;
rst_s2 <= 1'b0;
end
else begin
rst_s1 <= 1'b1;
rst_s2 <= rst_s1;
end
assign rst_sync_n = rst_s2;
always @ (posedge clk, negedge rst_sync_n)begin
if(!rst_sync_n)
b <= 0;
else
b <= a;
end
endmodule
Testbench;
`timescale 1ns/1ns
module dff_sync_tb();
reg clk;
reg rst_async_n;
reg a;
wire rst_sync_n;
wire b;
dff_sync inst(
.clk(clk),
.rst_async_n(rst_async_n),
.a(a),
.rst_sync_n(rst_sync_n),
.b(b)
);
always #10 clk = ~clk;
initial begin
clk = 0;
rst_async_n = 0;
a =0;
#15
rst_async_n = 1;
a = 1;
#15
rst_async_n = 0;
a = 1;
#20
rst_async_n =1;
a = 1;
#50
$stop;
end
endmodule