一、为什么要做跨时钟域处理
在之前的文章《FPGA静态时序分析与约束(一)、理解亚稳态》中,我知道了什么是亚稳态以及亚稳态对系统的危害。通常我们的系统工程中不止有一个处理时钟,当不同时钟域下的信号进行交互的时候就涉及到跨时钟域的问题了。由于不同时钟的频率、相位都可能不同,所以就存在目标时钟在采集源时钟域信号时发生亚稳态情况,如下图所示:
此时源时钟域下的信号变化刚好在目标时钟域的上升沿建立或保持时间范围内,因此发生了建立或保持时间违规从而造成亚稳态的输出。因此我们必须对这种情况进行处理,处理方式分为三种情况:
- 单bit信号从慢时钟到快时钟
- 单bit信号从快时钟到慢时钟
- 多bit信号跨时钟域
二、单bit信号从慢时钟到快时钟处理
2.1 使用同步寄存器链(打两拍)
在快时钟域频率高于慢时钟域时,理论上一个慢时钟域周期的信号可以百分百被快时钟采集到,为了防止亚稳态的产生可以通过打两拍的方式处理,如下图所示:
就算第一级寄存器产生了亚稳态,但是经过自身寄存器输出后能够使信号快速回落至稳定状态,稳定输出的概率为70%~80%左右,第二级寄存器可以稳定输出的概率为99%左右,再后面改善就不明显了,所以一般情况下进行两级寄存就能消除大多数情况下的亚稳态。代码如下:
`timescale 1ns / 1ps
module single_bit_slow2fast(
input i_signal_a , //慢时钟域的信号
input i_clk_b , //快时钟
input i_rst_b , //快时钟域复位信号,高电平有效
output o_signal_b , //快时钟域同步后的信号
output o_signal_b_pos , //同步后的信号上升沿
output o_signal_b_neg //同步后的信号下降沿
);
reg r_signal_a_d1 ;
reg r_signal_a_d2 ;
reg r_signal_a_d3 ; //再打一拍是用最稳定的两拍检测边沿信号
assign o_signal_b = r_signal_a_d2;
assign o_signal_b_pos = r_signal_a_d2 & (~r_signal_a_d3);
assign o_signal_b_neg = (~r_signal_a_d2) & r_signal_a_d3;
always @(posedge i_clk_b or posedge i_rst_b) begin
if(i_rst_b == 1'b1)begin
r_signal_a_d1 <= 1'b0;
r_signal_a_d2 <= 1'b0;
r_signal_a_d3 <= 1'b0;
end
else begin
r_signal_a_d1 <= i_signal_a;
r_signal_a_d2 <= r_signal_a_d1;
r_signal_a_d3 <= r_signal_a_d2;
end
end
endmodule
2.2 仿真代码编写
仿真代码产生两个不同快慢时钟,以及慢时钟域的信号,代码如下:
`timescale 1ns / 1ns
module tb_single_bit_slow2fast();
reg i_clk_b ;
reg clk_a ;
reg i_rst_b ;
reg i_signal_a ;
initial begin
i_clk_b = 0;
clk_a = 0;
i_rst_b = 1;
i_signal_a = 0;
#300 @(posedge i_clk_b) i_rst_b = 0;
end
always #2 i_clk_b = ~ i_clk_b;
always #15 clk_a = ~clk_a;
always @(posedge clk_a) begin
repeat(20)begin
i_signal_a <= {$random}%2;
#30
i_signal_a <= 0;
#200;
end
end
single_bit_slow2fast u_single_bit_slow2fast(
.i_signal_a ( i_signal_a ),
.i_clk_b ( i_clk_b ),
.i_rst_b ( i_rst_b ),
.o_signal_b ( o_signal_b ),
.o_signal_b_pos ( o_signal_b_pos ),
.o_signal_b_neg ( o_signal_b_neg )
);
endmodule
2.3 仿真结果观察
从仿真来看,慢时钟信号无论什么时候发送,快时钟依然能采集到。
三、单bit信号从快时钟域到慢时钟域处理
3.1 使用脉冲展宽
对于快时钟域的信号让慢时钟来采集,不能使用打两拍来处理,因为快时钟信号宽度太小很可能慢时钟采集不到,如下图这种情况:
在这种情况下,我们可以将快时钟下的信号进行展宽至慢时钟能采集到为止,然后再用慢时钟打两拍进行同步处理,如下所示:
具体展宽多长,需要看两个时钟频率之间的比值,例如快时钟频率是175.5M,慢时钟是27M,快慢时钟之比=6.5,所以要将快时钟的脉冲展宽至7个时钟周期宽度才能确保被慢时钟采到。代码如下:
3.1.1 Verilog代码编写
`timescale 1ns / 1ps
module single_bit_fast2slow#
(
parameter CLKA_FPQ = 100_000_000,
parameter CLKB_FPQ = 33_000_000
)
(
input i_clk_a , //快时钟
input i_rst_a , //快时钟域复位信号,高电平有效
input i_signal_a , //快时钟脉冲信号
input i_clk_b , //慢时钟
input i_rst_b , //慢时钟复位信号,高电平有效
output o_signal_b //慢时钟同步后的信号
);
localparam CLK_NUM = CLKA_FPQ / CLKB_FPQ;
/***********a时钟域*******************/
reg [7:0] clk_cnt ; //快时钟域下的计数器
reg r_i_signal_a ; //需要展宽的信号
always @(posedge i_clk_a) begin
if(i_rst_a)
r_i_signal_a <= 1'b0;
else if(clk_cnt == CLK_NUM)
r_i_signal_a <= 1'b0;
else if(i_signal_a == 1'b1)
r_i_signal_a <= 1'b1;
else
r_i_signal_a <= r_i_signal_a;
end
always @(posedge i_clk_a) begin
if(i_rst_a)
clk_cnt <= 'd0;
else if(clk_cnt == CLK_NUM)
clk_cnt <= 'd0;
else if(r_i_signal_a == 1'b1)
clk_cnt <= clk_cnt + 1'b1;
else
clk_cnt <= 'd0;
end
/******************b时钟域**************/
reg r_signal_b1 ;
reg r_signal_b2 ;
assign o_signal_b = r_signal_b2;
always @(posedge i_clk_b) begin
if(i_rst_b)begin
r_signal_b1 <= 1'b1;
r_signal_b2 <= 1'b1;
end
else begin
r_signal_b1 <= r_i_signal_a;
r_signal_b2 <= r_signal_b1;
end
end
endmodule
3.1.2 仿真代码编写
`timescale 1ns / 1ns
module tb_single_bit_fast2slow();
reg i_clk_a;
reg i_rst_a;
reg i_signal_a;
reg i_clk_b;
reg i_rst_b;
initial begin
i_clk_a = 0;
i_rst_a = 1;
i_clk_b = 0;
i_rst_b = 1;
#150
i_rst_a = 0;
#100
i_rst_b = 0;
end
always#5 i_clk_a = ~i_clk_a;
always#15 i_clk_b = ~i_clk_b;
always @(posedge i_clk_a) begin
if(i_rst_a == 1'b1)
i_signal_a <= 1'b0;
else begin
i_signal_a <= {$random}%2;
#10;
i_signal_a <= 1'b0;
#100;
end
end
single_bit_fast2slow#(
.CLKA_FPQ ( 100_000_000 ),
.CLKB_FPQ ( 30_000_000 )
)u_single_bit_fast2slow(
.i_clk_a ( i_clk_a ),
.i_rst_a ( i_rst_a ),
.i_signal_a ( i_signal_a ),
.i_clk_b ( i_clk_b ),
.i_rst_b ( i_rst_b ),
.o_signal_b ( o_signal_b )
);
endmodule
3.1.3 仿真结果观察
仿真设置快时钟频率为100M,慢时钟频率为30M,因此需要将脉冲宽度展宽四个快时钟周期,然后慢时钟再打两拍同步一下,由仿真结果观察结果正确,我们下面将快时钟设置为500M,结果如下:
由上面可以看出,快时钟为500M,慢时钟为30M依然也能正确的采集到快时钟的脉冲信号。
3.2 使用脉冲同步器
在快时钟域下把脉冲信号转成电平信号,再同步到慢时钟域。
3.2.1 Verilog代码编写
`timescale 1ns / 1ps
module single_bit_fast2slow(
input i_clk_a , //快时钟
input i_rst_a , //快时钟域复位信号,高电平有效
input i_signal_a , //快时钟脉冲信号
input i_clk_b , //慢时钟
input i_rst_b , //慢时钟复位信号,高电平有效
output o_signal_b //慢时钟同步后的信号
);
/***********a时钟域*******************/
reg r_i_signal_a ; //需要反转的信号
always @(posedge i_clk_a) begin
if(i_rst_a)
r_i_signal_a <= 1'b0;
else if(i_signal_a == 1'b1)
r_i_signal_a <= ~r_i_signal_a;
else
r_i_signal_a <= r_i_signal_a;
end
/******************b时钟域**************/
reg [2:0] r_signal_b ;//同步寄存器链
assign o_signal_b = r_signal_b[2] ^ r_signal_b[1]; //异或
always @(posedge i_clk_b) begin
if(i_rst_b)
r_signal_b <= 3'b000;
else
r_signal_b <= {r_signal_b[1:0],r_i_signal_a};
end
endmodule
3.2.2 仿真代码编写
`timescale 1ns / 1ns
module tb_single_bit_fast2slow();
reg i_clk_a;
reg i_rst_a;
reg i_signal_a;
reg i_clk_b;
reg i_rst_b;
initial begin
i_clk_a = 0;
i_rst_a = 1;
i_clk_b = 0;
i_rst_b = 1;
#150
i_rst_a = 0;
#100
i_rst_b = 0;
end
always#1 i_clk_a = ~i_clk_a;
always#15 i_clk_b = ~i_clk_b;
always @(posedge i_clk_a) begin
if(i_rst_a == 1'b1)
i_signal_a <= 1'b0;
else begin
i_signal_a <= {$random}%2;
#2;
i_signal_a <= 1'b0;
#100;
end
end
single_bit_fast2slow u_single_bit_fast2slow(
.i_clk_a ( i_clk_a ),
.i_rst_a ( i_rst_a ),
.i_signal_a ( i_signal_a ),
.i_clk_b ( i_clk_b ),
.i_rst_b ( i_rst_b ),
.o_signal_b ( o_signal_b )
);
endmodule
3.2.3 仿真结果观察
由上面可以看出,快时钟的脉冲信号能准确的同步到慢时钟域。
四、在任意时钟域跨单bit信号
前面两种情况,我们实现了单bit时钟从快到慢,或者从慢到快时钟之间的传输。除了使用以上两种方式外,我们还可以通过握手信号来实现任意时钟域之间的单bit信号传输。
4.1 使用握手协议传输单bit脉冲信号
从A时钟域向B时钟域传输脉冲信号,可以在A时钟检测脉冲信号,然后随即拉高一个valid信号,直到B时钟检测到valid信号后拉高一个周期的同步信号,同时给出ack信号,然后A时钟检测到ack信号后,拉低valid信号表示一次传输完成。
代码如下:
`timescale 1ns / 1ps
module single_bit_handshake#
(
parameter CLKA_FPQ = 100_000_000,
parameter CLKB_FPQ = 20_000_000
)
(
input i_clk_a , //A时钟
input i_rst_a , //A时钟域复位信号,高电平有效
input i_signal_a , //A时钟脉冲信号
input i_clk_b , //B时钟
input i_rst_b , //B时钟复位信号,高电平有效
output o_signal_b //B时钟同步后的信号
);
localparam CLK_NUM = (CLKA_FPQ >= CLKB_FPQ) ? 1 : (CLKB_FPQ / CLKA_FPQ);
/****************A时钟域*********************/
reg r_signal_a_valid ;
reg r_ack_a1 ;
reg r_ack_a2 ;
/****************B时钟域*********************/
reg r_signal_b1 ;
reg r_signal_b2 ;
reg r_signal_b3 ;
reg r_signal_b_ack ;
reg [7:0] r_clk_cnt_b ;
assign o_signal_b = r_signal_b3;
/****************A时钟域*********************/
always @(posedge i_clk_a) begin
if(i_rst_a == 1'b1)
r_signal_a_valid <= 1'b0;
else if(r_ack_a2 == 1'b1)
r_signal_a_valid <= 1'b0;
else if(i_signal_a == 1'b1)
r_signal_a_valid <= 1'b1;
else
r_signal_a_valid <= r_signal_a_valid;
end
always @(posedge i_clk_a) begin
if(i_rst_a == 1'b1)begin
r_ack_a1 <= 1'b0;
r_ack_a2 <= 1'b0;
end
else begin
r_ack_a1 <= r_signal_b_ack;
r_ack_a2 <= r_ack_a1;
end
end
/****************B时钟域*********************/
always @(posedge i_clk_b) begin
if(i_rst_b == 1'b1)begin
r_signal_b1 <= 1'b0;
r_signal_b2 <= 1'b0;
end
else if(r_signal_a_valid == 1'b1)begin
r_signal_b1 <= 1'b1;
r_signal_b2 <= r_signal_b1;
end
else begin
r_signal_b1 <= 1'b0;
r_signal_b2 <= 1'b0;
end
end
always @(posedge i_clk_b) begin
if(i_rst_b == 1'b1)
r_signal_b3 <= 1'b0;
else if((r_signal_b1 ==1'b1)&&(r_signal_b2 == 1'b0))
r_signal_b3 <= 1'b1;
else
r_signal_b3 <= 1'b0;
end
always @(posedge i_clk_b) begin
if(i_rst_b == 1'b1)
r_signal_b_ack <= 1'b0;
else if(r_clk_cnt_b == CLK_NUM)
r_signal_b_ack <= 1'b0;
else if((r_signal_b1 ==1'b1)&&(r_signal_b2 == 1'b0))
r_signal_b_ack <= 1'b1;
else
r_signal_b_ack <= r_signal_b_ack;
end
always @(posedge i_clk_b) begin
if(i_rst_b == 1'b1)
r_clk_cnt_b <= 'd0;
else if(r_clk_cnt_b == CLK_NUM)
r_clk_cnt_b <= 'd0;
else if(r_signal_b_ack == 1'b1)
r_clk_cnt_b <= r_clk_cnt_b + 1'b1;
else
r_clk_cnt_b <= 'd0;
end
endmodule
4.2 从快到慢仿真代码编写
先设置从快时钟到慢时钟
`timescale 1ns / 1ps
module tb_single_bit_handshake();
reg i_clk_a;
reg i_rst_a;
reg i_signal_a;
reg i_clk_b;
reg i_rst_b;
initial begin
i_clk_a = 0;
i_rst_a = 1;
i_clk_b = 0;
i_rst_b = 1;
#150
i_rst_a = 0;
#100
i_rst_b = 0;
end
always#1 i_clk_a = ~i_clk_a;
always#15 i_clk_b = ~i_clk_b;
always @(posedge i_clk_a) begin
if(i_rst_a == 1'b1)
i_signal_a <= 1'b0;
else begin
i_signal_a <= {$random}%2;
#2;
i_signal_a <= 1'b0;
#100;
end
end
single_bit_handshake#(
.CLKA_FPQ ( 500_000_000 ),
.CLKB_FPQ ( 30_000_000 )
)u_single_bit_handshake(
.i_clk_a ( i_clk_a ),
.i_rst_a ( i_rst_a ),
.i_signal_a ( i_signal_a ),
.i_clk_b ( i_clk_b ),
.i_rst_b ( i_rst_b ),
.o_signal_b ( o_signal_b )
);
endmodule
4.3 从快到慢仿真结果观察
由图可看出,从500M的快时钟到30M的慢时钟能成功传输。
4.4 从慢到快仿真结果观察
将仿真代码的AB时钟互换一下,变成从30M慢时钟传输到500M快时钟,仿真代码如下:
`timescale 1ns / 1ps
module tb_single_bit_handshake();
reg i_clk_a;
reg i_rst_a;
reg i_signal_a;
reg i_clk_b;
reg i_rst_b;
initial begin
i_clk_a = 0;
i_rst_a = 1;
i_clk_b = 0;
i_rst_b = 1;
#150
i_rst_a = 0;
#100
i_rst_b = 0;
end
always#15 i_clk_a = ~i_clk_a;
always#1 i_clk_b = ~i_clk_b;
always @(posedge i_clk_a) begin
if(i_rst_a == 1'b1)
i_signal_a <= 1'b0;
else begin
i_signal_a <= {$random}%2;
#30;
i_signal_a <= 1'b0;
#100;
end
end
single_bit_handshake#(
.CLKA_FPQ ( 30_000_000 ),
.CLKB_FPQ ( 500_000_000 )
)u_single_bit_handshake(
.i_clk_a ( i_clk_a ),
.i_rst_a ( i_rst_a ),
.i_signal_a ( i_signal_a ),
.i_clk_b ( i_clk_b ),
.i_rst_b ( i_rst_b ),
.o_signal_b ( o_signal_b )
);
endmodule
仿真结果如下:
由图可以看出,从慢时钟到快时钟依然能够同步成功。值得注意的是,前面这几种单bit跨时钟处理方法不适合连续的触发信号,如果需要同步连续的脉冲信号,则需要考虑使用异步fifo或者ram。
五、多bit数据跨时钟域
5.1 使用异步FIFO做跨时钟域
对于多bit情况,设计者必须习惯于采用例如双时钟 FIFO 这样的电路(DCFIFO)来存取数据和进行握手。FIFO 逻辑仅使用同步器传输转换两个时钟域之间的控制信号,而数据的读和写则使用双端口的存储器,具体FIFO的使用参考《详解Xilinx Native FIFO的使用以及RST复位的注意事项》。
5.1.1 异步fifo深度计算
FIFO的深度决定了其缓冲数据的能力,深度不够可能导致数据移除,深度过大会导致资源浪费,因此合理选择异步FIFO深度对整个设计有很大的帮助。一般使用情况是写速率大于读速率,且是突发性的写而不是一直写,如果一直写,无论多大的FIFO都会被写满。
-
写时钟>读时钟,读写时钟都是连续读写完一次突发
假如:写时钟wr_clk=100M,读时钟rd_clk=80M;一次突发数据量50000个,则FIFO最小深度是多少?
分析:wr_clk周期= 10ns,写50000个数据需要500000ns;rd_clk周期= 12.5ns,500000ns/12.5=40000个数据,所以FIFO最小深度=50000-40000=10000 -
写时钟>读时钟,读写时钟都是有空闲周期
假如:写时钟wr_clk=100M,写空闲3个周期;读时钟rd_clk=60M,读空闲2个周期;一次突发数据量50000个,则FIFO最小深度是多少?
分析:wr_clk周期= 10ns,写1个数据需要等3个周期,相当于4个周期写1个数据,所以写50000个数据需要2000000ns;rd_clk周期= 16.7ns,读1个数据需要等2个周期,相当于3个周期读1个,所以2000000ns/(16.7*3)=39920个数据,所以FIFO最小深度=50000-39920=10080个
5.2 使用格雷码做跨时钟域处理
六、Xilinx复位问题
根据Xilinx官方《WP272》指出:使用全局复位有利于我们仿真,因为这样所有的寄存器都是有初始值的,也可以在任意时刻让你的寄存器恢复初值,所以验证工程师很喜欢这样的设计,但是Xilinx建议的是尽量避免使用全局复位。
Altera的FPGA里面的flip-flop只支持低有效的异步复位,所以推荐使用低有效的异步复位。而Xilinx 7 系列架构中的器件每个 slice 包含 8 个寄存器,所有这些寄存器都是 D 型触发器。所有这些 flip-flops 共享一个公共控件集,flip-flop 的控制集是时钟输入 (CLK)、高电平有效芯片使能 (CE) 和高电平有效 SR 端口。触发器中的 SR 端口可以用作异步复/置位和同步复位/置位端口,如下图所示:
判断寄存器的 RTL 代码可以推断出flip-flop使用的reset 类型。当 reset 信号出现在 RTL 进程的敏感列表中时,代码将推断出 asynchronous reset ,其中 SR 端口配置为 preset 或 clear 端口(由 FDCE 或 FDPE flip-flop 原语表示),例如:
always @(posedge clk or posedge rst) begin
if(rst == 1'b1)
d <= 'd0;
else
d <= d;
end
当 reset 信号没有出现在 RTL 进程的敏感列表中时,代码将推断出 synchronous resets,其中 SR 端口配置为 set 或 reset 端口(由 FDSE 或 FDRE flip-flop 原语表示),例如:
always @(posedge clk ) begin
if(rst == 1'b1)
d <= 'd0;
else
d <= d;
end
6.1 D触发器综合后的类型
根据SR端口的属性,触发器会被综合成四种具体的触发器:FDCE、FDPE、FDRE和FDSE;其中“FD”为D型触发器,“C”为“Clear”、“P”为“Preset”、“R”为“Reset”、“S”为“Set”、“E”为“Clock Enable”;
- FDCE:带时钟使能和异步清除的 D 触发器
触发器原语如下:
FDCE: Single Data Rate D Flip-Flop with Asynchronous Clear and
// Clock Enable (posedge clk).
// 7 Series
// Xilinx HDL Libraries Guide, version 14.7
FDCE #(
.INIT(1'b0) // Initial value of register (1'b0 or 1'b1)
) FDCE_inst (
.Q(Q), // 1-bit Data output
.C(C), // 1-bit Clock input
.CE(CE), // 1-bit Clock enable input
.CLR(CLR), // 1-bit Asynchronous clear input
.D(D) // 1-bit Data input
);
// End of FDCE_inst instantiation
这是具有数据 (D)、时钟使能 (CE) 、异步清除(CLR)以及数据输出 (Q)的 D 型触发器。当时钟使能 (CE) 为高且异步清除 (CLR) 为低时,该触发器的数据输入 (D) 上的数据将在时钟 ( C ) 从上升沿传输到相应的数据输出 ( Q )。当 CLR 为高时,它会覆盖所有其他输入并将数据输出 ( Q ) 重置为低,逻辑真值表如下所示:
- FDPE:具有时钟使能和异步置位的 D 触发器
触发器原语如下:
FDPE: Single Data Rate D Flip-Flop with Asynchronous Preset and
-- Clock Enable (posedge clk).
-- 7 Series
-- Xilinx HDL Libraries Guide, version 14.7
FDPE_inst : FDPE
generic map (
INIT => '0') -- Initial value of register ('0' or '1')
port map (
Q => Q, -- Data output
C => C, -- Clock input
CE => CE, -- Clock enable input
PRE => PRE, -- Asynchronous preset input
D => D -- Data input
);
-- End of FDPE_inst instantiation
这具有数据 (D)、时钟使能 (CE) 和异步置位 (PRE) 输入以及数据输出 (Q)的 D 型触发器。异步 PRE 为高时会覆盖所有其他输入并将 (Q) 输出设置为高。当时钟 ( C ) 从上升沿时,当 PRE 为低且 CE 为高时,(D) 输入上的数据将加载到触发器中,逻辑真值表如下所示:
- FDRE:具有时钟使能和同步复位功能的 D 触发器
触发器原语如下:
-- FDRE: Single Data Rate D Flip-Flop with Synchronous Reset and
-- Clock Enable (posedge clk).
-- 7 Series
-- Xilinx HDL Libraries Guide, version 14.7
FDRE_inst : FDRE
generic map (
INIT => '0') -- Initial value of register ('0' or '1')
port map (
Q => Q, -- Data output
C => C, -- Clock input
CE => CE, -- Clock enable input
R => R, -- Synchronous reset input
D => D -- Data input
);
-- End of FDRE_inst instantiation
该具有数据 (D)、时钟使能 (CE) 和同步复位 ® 输入以及数据输出 (Q)的D触发器。同步复位 ® 输入为高电平时,将覆盖所有其他输入,并在时钟 ( C ) 从上升沿时将 (Q) 输出复位为低电平。在时钟从上升沿,当 R 为低电平且 CE 为高电平时,(D) 输入上的数据将加载到触发器中,逻辑真值表如下所示:
- FDSE:带时钟使能和同步设置的 D 触发器
触发器原语如下:
-- FDSE: Single Data Rate D Flip-Flop with Synchronous Set and
-- Clock Enable (posedge clk).
-- 7 Series
-- Xilinx HDL Libraries Guide, version 14.7
FDSE_inst : FDSE
generic map (
INIT => '0') -- Initial value of register ('0' or '1')
port map (
Q => Q, -- Data output
C => C, -- Clock input
CE => CE, -- Clock enable input
S => S, -- Synchronous Set input
D => D -- Data input
);
-- End of FDSE_inst instantiation
这是具有数据 (D)、时钟使能 (CE) 和同步设置 (S) 输入以及数据输出 (Q)的D触发器。同步设置 (S) 输入为高时,将覆盖时钟使能 (CE) 输入,并在时钟 ( C ) 上升沿将 Q 输出设置为高。当时钟 ©上升沿时,当 S 为低且 CE 为高时,D 输入上的数据将加载到触发器中。,其逻辑真值表如下所示:
6.2 怎样使用复位信号?
6.2.1 尽量不使用复位
正如本章开始所说:复位大部分是为了仿真能更好的观察,很多工程师习惯于对FPGA设计进行上电复位,总担心如果不复位,触发器就处于不定状态,导致系统跑飞。事实上,其实当Xilinx FPGA配置或重新配置时,所有的单元都会被初始化。每个触发器都有明确的初始值,这个初始值与是否复位无关。因此,一旦系统上电,即使没有复位,对于FDSE和FDPE,其初始值为1,对于FDRE和FDCE,其初始值为0。Block RAM和DSP48内部触发器初始值为0。以下几种情况也不需要使用复位:
- 数据信号不需要复位:因为旧数据会被新数据覆盖,我们设计一般都是在数据更新后使用新数据。
- 功能仿真中不需要复位:在仿真中为获得触发器初始值,可在定义该触发器时直接声明,无需复位
6.2.2 尽量使用局部复位
因为当全局异步复位时信号线接到“SR”端口,随着器件资源增多,频率提高,全局复位就会造成信号扇出增大,增加时序的复杂些,布局时序难以得到满足,那么依旧会导致在复位释放时出现亚稳态的情况,所以选择局部复位,更有利地使得时序收敛,官方也给出了扇出与时钟频率关系的指南:
6.2.3 使用同步高电平复位
如需复位,AMD 建议使用同步复位。同步复位相比于异步复位具有如下优势:
- 同步复位可以直接映射至器件架构中的更多资源元件
- 异步复位会影响通用逻辑结构的最大时钟频率。由于所有 AMD 器件的通用寄存器均可将置位/复位编程为异步或同
步,可能看似使用异步复位不会受到任何惩罚。使用全局异步复位并不会增加控制集。但由于需要将此复位信号布
局到所有寄存器元件,因此会增加布线复杂性。 - DSP48 和块 RAM 等部分资源仅包含同步复位以供块内的寄存器元件使用。在与这些元件关联的寄存器元件上使用
异步复位时,可能无法在不影响功能的前提下直接将这些寄存器推断到这些块中,所以当使用DSP48或者RAM时,使用异步复位,可能会增加我们的综合后的资源量,例如:
- 同步复位代码如下:
module test_rst(
input clk ,
input rst ,
input [17:0] data_a ,
input [17:0] data_b ,
output reg [35:0] data_c
);
reg [17:0] data_a_r1 ;
reg [17:0] data_b_r1 ;
always @(posedge clk) begin
if(rst == 1'b1)begin
data_a_r1 <= 'd0;
data_b_r1 <= 'd0;
end
else begin
data_a_r1 <= data_a;
data_b_r1 <= data_b;
end
end
always @(posedge clk) begin
if(rst == 1'b1)begin
data_c <= 'd0;
end
else begin
data_c <= data_a_r1 * data_a_r1;
end
end
endmodule
综合后的资源情况
综合后每一个寄存器被综合成FDRE,一共消耗了16个LUT、18个FF和1个DSP。
- 异步复位代码:
module test_rst(
input clk ,
input rst ,
input [17:0] data_a ,
input [17:0] data_b ,
output reg [35:0] data_c
);
reg [17:0] data_a_r1 ;
reg [17:0] data_b_r1 ;
always @(posedge clk or posedge rst) begin
if(rst == 1'b1)begin
data_a_r1 <= 'd0;
data_b_r1 <= 'd0;
end
else begin
data_a_r1 <= data_a;
data_b_r1 <= data_b;
end
end
always @(posedge clk or posedge rst) begin
if(rst == 1'b1)begin
data_c <= 'd0;
end
else begin
data_c <= data_a_r1 * data_a_r1;
end
end
endmodule
综合后的资源情况:
综合后每一个寄存器被综合成FDCE,一共消耗了35个LUT、19个FF和1个DSP,由此可见异步复位会增加更多的资源消耗
6.2.4 使用异步复位同步释放
如果有些设计要求必须使用异步复位,例如来自于外部的复位芯片、按键等等等,那么建议使用异步复位同步释放。
module rst_release(
input clk,
input rst_async,
output reg rst_sync
);
reg rst_async_d1;
always @(posedge clk or posedge rst_async) begin
if(rst_async) begin
rst_async_d1 <= 1'b1;
rst_sync <= 1'b1;
end
else begin
rst_async_d1 <= 1'b0;
rst_sync<= rst_async_d1 ;
end
end
endmodule