以下内容摘自:《正点原子逻辑设计指南》
逻辑设计中一般存在多个时钟,那么信号在多个时钟之间如何切换呢?这个就涉及到了异步电路设计,异步电路设计也是逻辑设计中非常重要的设计,可以说异步处理不好,逻辑出问题的可能性非常大。
一、亚稳态简介
在介绍异步设计方法之前,我们先了解下亚稳定的概念,为什么要先了解亚稳态呢?因为异步电路如果直接使用寄存器采样会出现问题,最常见的就是出现亚稳态。寄存器采样需要满足一定的建立时间(setup)和保持时间(holdup),而异步电路没有办法保证建立时间(setup)和保持时间(holdup),所以会出现亚稳态。
下图为两个不同时钟的寄存器的示意图,寄存器 B 采样可能采样到寄存器 A 输出的任意状态,包括 Q 的信号跳变沿。
但是寄存器的 D 端信号需要满足建立时间 Tsu 和保持时间 Th 要求,否则就会出现 Q 端采样到不确定的值的状态,就是俗称的亚稳态。
异步电路设计就是要解决亚稳态的问题。异步电路设计一般有几个方法:
- 单 bit 信号:直接多级寄存器同步法,一般采用 2-3 级寄存器进行同步处理,这个 2-3 级寄存器也称作同步器,在ASIC 设计中,一般都有提供专用的同步器库,因为同步器要求多级寄存器位置靠的越近越好,靠的越近,亚稳态消失的概率就越大。FPGA 设计中,直接使用 2-3 级寄存器进行同步处理即可。
如上图所示就是一个正常第一级寄存器发生了亚稳态,第二级、第三极寄存器消除亚稳态时序模型。可以看出,当第一个寄存器发生亚稳态后,经过Tmet的振荡稳定后,第二级寄存器能采集到一个稳定的值。
- 多 bit 信号:异步 FIFO 或者使用多次握手同步方法。在握手协议中,异步的 REQ/ACK 也需要使用单 bit 同步技术进行同步处理,异步 FIFO 也是如此。
针对单 bit 信号,可以再细分为慢时钟信号同步到快时钟和快时钟信号同步到慢时钟两个情况。信号在慢时钟到快时钟域传递,且两个时钟相差比较大时,慢时钟的脉冲可以被快时钟当做电平,使用多级寄存器同步后,在采用边沿检测电路即可得到相应的脉冲信号。信号在快时钟到慢时钟域传递,即使使用同步器同步,也很可能采样不到快时钟信号,所以此时需要先对快时钟信号进行展宽处理(就是同一个信号先在快时钟进行多次寄存,然后将多个打拍延迟不同的信号或起来),然后再进行慢时钟同步。
多 bit 信号最常用的就是异步 FIFO,关于异步 FIFO的设计请参考另一篇文章:异步 FIFO 设计。
二、实验任务
使用 Verilog 语言设计一个单 bit 信号从快时钟切换到慢时钟。
单 bit 信号从快时钟切换到慢时钟,我们需要先对这个信号进行展宽处理,然后再进行同步处理。
我们假定有两个时钟,CLK1 和 CLK2,还有一个信号叫 READ,CLK1 时钟频率快于 CLK2,READ 信号是 CLK1 时钟产生的,READ_DLY1 信号是 READ 信号相对于 CLK1 时钟打一拍产生的,READ_DLY2 信号是 READ 信号相对于 CLK1 时钟打两拍产生的,由于单纯的 READ 信号宽度根本不够 CLK2 采样,所以需要展宽 READ 的信号宽度, READ_OR 信号是由 READ 和 READ_DLY1 以及READ_DLY2 相或产生的。或之后 READ_OR 信号宽度以及够 CLK2 采样。同步原理如下,直接使用 CLK2 采样 READ_OR 信号得到 READ_ SYNC,然后再对READ_SYNC 打 2 拍,第一拍得到 READ_ SYNC_DLY1,第二拍得到 READ SYNC_DLY2,然后READ_SYNC_DLY1 和 READ_OR_SYNC_DLY2 的取反信号相与得到 READ SYNC_PULSE,即已经同步到 CLK2 时钟域的 READ 信号上升沿指示信号。
对 READ_SYNC 打 2 拍目的是消除亚稳态,打两拍之后的亚稳态概率已经非常非常小了,由于有电路噪声,所以寄存器会恢复到固定电平。我们画出如下的时序图。
三、程序设计
1、RTL代码
module asyn_process (
input clk1 , //快时钟信号
input read , //信号,快时钟阈的
input clk2 , //慢时钟信号
input sys_rst_n , //复位信号,低电平有效
output wire read_sync_pulse //输出信号
);
//reg define
reg read_dly1 ;
reg read_dly2 ;
reg read_or ;
reg read_sync ;
reg read_sync_dly1 ;
reg read_sync_dly2 ;
//*****************************************************
//** main code
//*****************************************************
always @(posedge clk1 or negedge sys_rst_n) begin
if (sys_rst_n ==1'b0)
read_dly1 <= 1'b0;
else
read_dly1 <=read;
end
always @(posedge clk1 or negedge sys_rst_n) begin
if (sys_rst_n ==1'b0)
read_dly2 <= 1'b0;
else
read_dly2 <= read_dly1 ;
end
always @(posedge clk1 or negedge sys_rst_n) begin
if (sys_rst_n ==1'b0)
read_or <= 1'b0;
else
read_or <= read | read_dly1 | read_dly2;
end
always @(posedge clk2 or negedge sys_rst_n) begin
if (sys_rst_n ==1'b0) begin
read_sync <= 1'b0;
end
else
read_sync <= read_or;
end
always @(posedge clk2 or negedge sys_rst_n) begin
if (sys_rst_n ==1'b0)
read_sync_dly1 <= 1'b0;
else
read_sync_dly1 <= read_sync;
end
always @(posedge clk2 or negedge sys_rst_n) begin
if (sys_rst_n ==1'b0)
read_sync_dly2<= 1'b0;
else
read_sync_dly2 <= read_sync_dly1;
end
assign read_sync_pulse = read_sync_dly1 & ~read_sync_dly2;
endmodule
2、测试代码
`timescale 1ns / 1ps
module TB();
reg sys_clk1;
reg sys_clk2;
reg sys_rst_n;
reg read ;
initial begin
sys_clk1 = 1'b0;
sys_clk2 = 1'b0;
sys_rst_n = 1'b0;
read = 1'b0;
#200
sys_rst_n = 1'b1;
#100
read = 1'b1;
#100
read = 1'b0;
#100
read = 1'b1;
end
always #10 sys_clk1 = ~sys_clk1;
always #30 sys_clk2 = ~sys_clk2;
asyn_process u_asyn_process(
.clk1 (sys_clk1 ),
.clk2 (sys_clk2 ),
.sys_rst_n (sys_rst_n),
.read (read ),
.read_sync_pulse(read_sync_pulse )
);
endmodule
3、仿真结果
注意,在波形仿真中是不会出现亚稳态的,但是在实际电路中,亚稳态是实实在在存在的,一定要严格遵守亚稳态的处理规则。