写在前面的话
在FPGA设计中,很多同学会纠结到底是应该使用同步复位还是应该使用异步复位。实际上,无论是同步复位还是异步复位都有各自的优缺点。在这里梦翼师兄和大家一起学习另外一种复位信号的处理方式-异步复位同步释放。
基本概念
FPGA设计中常见的复位方式有同步复位和异步复位,同步复位就是指复位信号只有在时钟上升沿到来时,才能有效;异步复位是指无论时钟沿是否到来,只要复位信号有效,就对系统进行复位;这两种复位方式在实际应用中都有其弊端存在,所以,一般都推荐使用异步复位同步释放的方式,是提高系统稳定性的有效方式,下面来介绍前面两种复位方式,我们以此来比较说明异步复位同步释放的优点。
代码举例
下面是一个简单的同步复位的代码:
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function:同步复位模块*****************************************************/ 01 module syn( 02 clk, //系统时钟50Mhz 03 rst_n, //系统低电平复位 04 d, //外部输入 05 q //输出 06 ); 07 08 input clk; 09 input rst_n; 10 input d; 11 12 output reg q; 13 14 always @ (posedge clk) //同步复位 15 begin 16 if (!rst_n) 17 q <= 0; //复位的时候清零 18 else 19 q <= d; //置位的时候输出d 20 end 21 22 endmodule |
该代码的RTL图如下:
从RTL视图我们可以看出,同步复位综合出来的实际电路只是把复位信号rst_n做为了输入逻辑的使能信号,经过一个数据选择器最后输出到寄存器,很明显,这样的同步复位电路势必会额外增加FPGA内部的资源消耗。
下面是一个简单的异步复位的代码:
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function : 异步复位模块*****************************************************/ 01 module asy( 02 clk, //系统时钟50Mhz 03 rst_n, //系统低电平复位 04 d, //外部输入 05 q //输出 06 ); 07 08 input clk; 09 input rst_n; 10 input d; 11 12 output reg q; 13 14 always @ (posedge clk or negedge rst_n) //异步复位 15 begin 16 if (!rst_n) 17 q <= 0; //复位的时候清零 18 else 19 q <= d; //置位的时候输出d 20 end 21 22 endmodule |
该代码的RTL图如下:
我们可以看到寄存器有一个异步的清零端(CLR),在异步复位的设计中这个端口一般就是接低电平有效的复位信号rst_n。如果我们设计的是高电平复位,那么实际综合后会也会将该复位信号反向后接这个CLR端。
总结:同步复位的优点是它只在时钟信号clk的上升沿触发进行系统是否复位的判断,这降低了亚稳态出现的概率;缺点在于它需要消耗更多的器件资源。FPGA的寄存器有支持异步复位专用的端口,采用异步复位的端口无需额外增加器件资源的消耗,但是异步复位也存在着隐患,比如: 在同步复位和异步复位电路中复位信号刚好由复位状态跳变到置位状态时,时钟上升沿刚好到来,那么时钟采集的数据0还是1呢?
当上述情况发生时,电路会进入亚稳态。亚稳态是在寄存器的建立时间和保持时间不满足的情况下发生的。当亚稳态发生时采集到的数据是一个不定态,有可能是1也有可能是0,所以无法确保所有的寄存器在同一个时钟沿跳出复位状态。
上面的分析似乎都让人觉得同步复位和异步复位都不可靠,那么如何将两者结合,取长补短呢?下面我们来介绍一种异步复位同步释放的电路。
异步复位同步释放电路
观察这个电路图可以看出reset_n接到两个异步复位寄存器的清零端(CLRN端接低电平的时候寄存器输出端会清零),当reset_n为0 时,寄存器reg3和寄存器reg4输出为0;由于寄存器reg1和寄存器reg2的CLRN端接到了寄存器reg4的输出端,所以寄存器输出端out_a和out_b会被清零,从而实现复位清零的功能。在reset_n由低变高时,第一个时钟周期将VCC输入到左边第一个寄存器,第二个寄存器保持为0,在第二个时钟周期后两个寄存器都变为1,输出端寄存器的清零端为1,跳出复位。
将上图电路描述为代码如下:
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function : 异步复位同步释放模块*****************************************************/ 01 module sync_async_reset( 02 clk, 03 rst_n, 04 data_a, 05 data_b, 06 out_a, 07 out_b 08 ); 09 10 input clk; 11 input rst_n; 12 input data_a; 13 input data_b; 14 15 output out_a; 16 output out_b; 17 18 reg reg1,reg2; 19 reg reg3,reg4; 20 21 wire reset_n; 22 23 assign out_a = reg1; //将reg1赋值给输出 out_a; 24 assign out_b = reg2; //将reg2赋值给输出 out_b; 25 assign reset_n = reg4; //将寄存器的值赋给reset_n 26 //产生同步复位输出reset_n,reset_n作为第二个进程模块的异步复位。 27 always@(posedge clk or negedge rst_n) 28 begin 29 if(!rst_n) 30 begin 31 reg3 <= 1'b0; 32 reg4 <= 1'b0; 33 end 34 else 35 begin 36 reg3 <= 1'b1; 37 reg4 <= reg3; 38 end 39 end 40 //将这个已经同步化了的复位信号当作异步复位使用。 41 always@(posedge clk or negedge reset_n) 42 begin 43 if(!reset_n) 44 begin 45 reg1 <= 1'b0; 46 reg2 <= 1'b0; 47 end 48 else 49 begin 50 reg1 <= data_a; 51 reg2 <= data_b; 52 end 53 end 54 endmodule |
第27~39行定义了2个寄存器,对应于我们电路图中的reg3和reg4,目的是产生同步复位输出reset_n;第41~53行也定义了两个寄存器,对应于我们电路图中的reg1和reg2,输入的复位信号是reset_n;第23~25行只做连线。
测试代码如下:
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function : 异步复位同步释放测试模块*****************************************************/ 01 `timescale 1ns/1ps 02 module tb; 03 04 reg clk; 05 reg rst_n; 06 reg data_a; //外部输入 07 reg data_b; //外部输入 08 wire out_a; //输出 09 wire out_b; //输出 10 11 initial begin 12 clk = 1; 13 rst_n = 0; data_a = 0; data_b = 0; 14 #100 rst_n = 1; 15 #100 data_a = 1; data_b = 1; //输入4组数据 16 #100 data_a = 1; data_b = 0; 17 #100 data_a = 0; data_b = 1; 18 #100 data_a = 0; data_b = 0; 19 end 20 21 always #10 clk = ~clk; //产生周期是20ns的时钟 22 23 sync_async_reset sync_async_reset( 24 .clk(clk), 25 .rst_n(rst_n), 26 .data_a(data_a), 27 .data_b(data_b), 28 .out_a(out_a), 29 .out_b(out_b) 30 ); 31 32 endmodule |
仿真波形如下:
观察波形可以看出,我们外部输入的复位信号rst_n是一个异步复位,产生的reset_n是一个同步信号,它用于其他模块的输入的复位信号。
同理下面介绍如何使用锁相环进行异步复位同步释放,复位电路原理图如下:
要理解此电路,我们必须掌握以下几点:
locked信号为锁相环的输出信号,锁相环时钟输出端CO在上电以后会有一段不稳定的时间,此时locked信号为低电平,当时钟输出端C0输出保持稳定以后,locked信号也会同步拉高,表示输出有效。
areset为输入锁相环的高电平复位信号,当areset为高电平时,锁相环复位,没有时钟输出。
寄存器ENA端为寄存器输出使能,高电平有效,只有当ENA保持为高电平的时候,寄存器才会有数据输出。
由以上电路原理图可以看出,当reset_n信号变为低电平以后,寄存器reg1、reg2、reg3、reg4都会清零,由于reset_n到areset端口之间经过了一个非门,电平取反,对于锁相环来说,复位端为高电平,可以实现复位。
当reset_n信号由低电平变为高电平以后,锁相环复位和所有寄存器清零同步结束,但由于锁相环的输出端locked信号需要稳定一定时间才能输出高电平而且寄存器reg3和reg4的输出使能端有locked信号控制,所以必须要等到锁相环输出稳定以后,VCC才会开始在寄存器reg3和reg4之间传递,使其他寄存器电路正式结束复位状态。
上述电路图的代码描述如下:
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function : 异步复位同步释放加锁相环模块*****************************************************/ 00 module sync_async_reset_pll( 01 clk, //系统50Mhz时钟 02 rst_n, //系统低电平复位 03 data_a, //寄存器reg1输入 04 data_b, //寄存器reg2输入 05 out_a, //寄存器reg1输出 06 out_b //寄存器reg2输出 07 ); 08 09 input clk; 10 input rst_n; 11 input data_a; 12 input data_b; 13 14 output out_a; 15 output out_b; 16 17 reg reg1,reg2; 18 reg reg3,reg4; 19 20 wire reset_n; 21 wire clk_out; 22 wire lock; 23 assign out_a =reg1; //将reg1赋值给输出 out_a; 24 assign out_b =reg2; //将reg2赋值给输出 out_b; 25 assign reset_n =reg4; //将寄存器的值赋给reset_n 26 27 always@(posedge clk_out or negedge rst_n) 28 begin 29 if(!rst_n) 30 begin 31 reg3 <= 1'b0; 32 reg4 <= 1'b0; 33 end 34 //lock信号有效,输出同步复位信号 reset_n 35 else if(lock) //reset_n作为第二个进程模块的异步复位 36 begin 37 reg3 <= 1'b1; 38 reg4 <= reg3; 39 end 40 else //lock信号无效,输出保持上一个状态 41 begin 42 reg3 <= reg3; 43 reg4 <= reg4; 44 end 45 end 46 47 //将这个已经同步化了的复位信号当作异步复位使用 48 always@(posedge clk_out or negedge reset_n) 49 begin 50 if(!reset_n) 51 begin 52 reg1 <= 1'b0; 53 reg2 <= 1'b0; 54 end 55 else 56 begin 57 reg1 <= data_a; 58 reg2 <= data_b; 59 end 60 end 61 62 my_pll my_pll_inst ( 63 .areset ( !rst_n ), 64 .inclk0 ( clk ), 65 .c0 ( clk_out ), 66 .locked ( lock ) 67 ); 68 69 endmodule |
测试代码如下:
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function : 异步复位同步释放加锁相环测试模块*****************************************************/ 01 `timescale 1ns/1ps 02 module tb; 03 04 reg clk; 05 reg rst_n; 06 reg data_a; //外部输入 07 reg data_b; //外部输入 08 wire out_a; //输出 09 wire out_b; //输出 10 11 initial begin 12 clk = 1; 13 rst_n = 0; data_a = 0; data_b = 0; 14 #100 rst_n = 1; 15 #100 data_a = 1; data_b = 1; //输入4组数据 16 #100 data_a = 1; data_b = 0; 17 #100 data_a = 0; data_b = 1; 18 #100 data_a = 0; data_b = 0; 19 end 20 21 always #10 clk = ~clk; //产生周期是20ns的时钟 22 23 sync_async_reset_pll sync_async_reset_pll( 24 .clk(clk), 25 .rst_n(rst_n), 26 .data_a(data_a), 27 .data_b(data_b), 28 .out_a(out_a), 29 .out_b(out_b) 30 ); 31 32 endmodule |
15~18输入4组数据观察输出波形
仿真波形如下:
观察波形可知,当外部复位按键rst_n放开之后,锁相环开始动作,clk_out持续一段时间不定态以后开始输出稳定的方波信号,同时lock信号置为高电平。Lock信号置为高电平之后使能reg3和reg4,reg4比reg3晚一拍输出高电平,当reg4输出高电平之后,out_a和out_b才分别等于data_a和data_b。这与我们的分析是一致的,所以我们本次设计正确。