时钟切换 glitch-free

刚入行时最早看到的时钟切换电路出自下面这篇文献,第20页,Trouble-Free Switching Between Clocks。 这篇极短的小文章可能是 Xilinx 元老 Peter Alfke 写的。 Peter 是我非常敬重的前辈,读过几篇他写的数字电路小文章,非常实用,受益匪浅。

https://www.xilinx.com/publications/archives/xcell/Xcell24.pdf
在这里插入图片描述
这个电路虽然概念上是对的,但有明显瑕疵。 如果 Select 相对于 Clock A, Clock B 是完全异步 asynchronous,电路中的两个 flip flop 在 Select 变化时会有亚稳态 metastability的可能性。 克服这个瑕疵的 fix 很简单,把图中的单个 flip flop 换成两级 flip flop 组成的同步器 synchronizer 就可以了。

Xilinx这个电路的基本思想是 SR latch。 去掉图中两个 flip flop ,最前端的两个 AND 组成一个类似 SR latch 的电路,只不过 SR Latch 的 S,R 输入换成了 Select 和 Select inverted。 为了使 SR Latch 的输出能与所需控制的时钟同步,加上了一级 flip flop 同步一下。 当然,两级 flip flop 同步会更好。Xilinx这个电路里有一个非常重要的小细节,牵涉到时钟切换的一条重要原则 — 先关断当前选择的时钟,再使能新选择的时钟。 QB 反馈到 Clock A 那边经过一个反向再和 Select 反向 AND,QA 反馈到 Clock B 那边经过一个反向再和Select AND,保证了先关断再使能这个重要原则。

Xilinx这个电路里还有个非常重要的小细节,也是该电路的精妙之处。 这两个 flip flop 的时钟输入端有个小泡泡,意思是时钟下降沿触发。这是一个重要的设计思想。如果要 gate off 掉一个时钟,在该时钟低电平时gate off 是最安全的。 如果是在高电平时做切换,很容易造成高电平被切掉一段变成一个毛刺,这可是大大的危害。 搞笑的是网上 随便搜一下 “glitchless clock switching”,会出来一大堆文章,虽然都是基于Xilinx这个电路,但大部分漏掉了低电平触发这个细节。没有完全理解就胡乱抄胡乱吹牛,比较符合南亚某大国的国民性格。嘴上吹得天花乱坠,做事毛毛躁躁。做出来的电路有时工作有时不工作。玩具上用用倒也没大问题。 这种态度用到飞机上就是 737 MAX。

下面是改进的电路,一级 flip flop 换成了两级 flip flop 同步而已。
在这里插入图片描述
电路启动波形

在这里插入图片描述
切换波形 – fast to slow
在这里插入图片描述
切换波形 – slow to fast
在这里插入图片描述
这个改进电路依然有一些可以变化的地方。 譬如电路中的 AND换成 ICG 貌似更好一些,对后端的工具更友好一些,实际上ICG在初始状态有一个额外要求,不见得是好的选择,见补充2。

以上是一个比较经典的时钟切换电路。 根据实际使用场景的不同,时钟切换有很多不同的实现方法,都可以做得非常经典。 时钟,复位,是数字设计里最最基本的电路,稍有不慎,就会毁了整个设计,一定要谨慎再谨慎。

顺便讲一下,在芯片设计里,这种特殊电路都应该是例化instantiate 的。 例化的好处是便于在后端流程里找到这些门。例如,这里的AND 可以用一个 bbox_and 模块。bbox_and 里例化 instantiate 库里的 CKAND2 门。时钟切换电路里例化 bbox_and 模块,给个 u_bbox_and_1 这样的 instance name。 在后端设计里,就可以很方便找到 u_clkgen/u_bbox_and_1/bbox_and_0 ,加约束就会很方便。 使用 bbox 模块也是数字设计中一个重要技巧,以后有时间再讨论了。

小结一下。

1) 时钟切换的重要原则 — 先关断当前选择的时钟,再使能新选择的时钟。

2) 当前时钟低电平时切换是比较安全的做法。

补充一下。 上面的电路只是概念上的示意图。 实际设计中要考虑 DFT, 要加 DFT clock mux, DFT reset mux,会复杂一些。

补充2

前面讲了可以考虑把AND换成ICG。一位网友指出,换成ICG后如果时钟的初始值时高电平,电路可能会出问题。 首先感谢这位网友,谢谢指出这个与ICG特性有关的重要问题。

下面是改用ICG后的时钟切换电路。
在这里插入图片描述
下面是在clk1, clk2初始值都为高的场景下该电路启动时的仿真波形。在这个特殊场景下,由于clk1, clk2初始为高,使得对应的ICG输出clk_slow_gated, clk_fast_gated初始值为X,造成切换电路输出在初始时也为X!造成这个现象的是ICG的一个重要“缺陷” – 未被初始化uninitialized ICG。
在这里插入图片描述
解释这个现象,首先要先看一下这个电路里用到的ICG的内部构造。这里SE(scan mode ICG enable),E(functional mode ICG enable)是latch的数据输入。Latch的gate信号为CK的反向。

CK为0时latch打开,(E | SE)通过latch,ICG的输出 = (E|SE)& CK。因为此时CK为0,ICG的输出也为0。

CK为1时latch关闭并保持以前的状态(latch_old_state)。假设CK变为1前E=1,latch_old_state =1,ICG的输出=latch_old_state & CK= CK;假设CK变为1前E=0,latch_old_state =0,ICG的输出=0 & CK= 0。
在这里插入图片描述
如果初始时CLK为1,ICG内部的latch是关闭的,E无法通过,而且因为之前latch从来没有打开过,没锁存过任何确定的值,latch_old_state 不确定 (仿真中显示为X)。在这个场景下ICG输出= latch_old_state & CK= X& CK= X,即ICG输出为X。下面就是这个场景的波形。

如果初始时CLK为0,ICG内部的latch是打开的, ICG的输出为 (E & CLK) = 0,ICG输出为确定值,ICG被成功初始化。下面是这个场景下的仿真波形。
在这里插入图片描述
综上所述,为了避免上述ICG未初始化引起的问题,使用ICG搭时钟切换电路时,系统设计中要考虑加一个时钟初始为0的要求。

补充3

基于补充2的电路,还有一种变化,就是把falling edge DFF换成rising edge DFF。这时要特别小心两个时钟周期相差很大的情况。例如一路时钟为clk_slow (slow clock), 另一路为clk_fast (fast clock),选择信号sel由0变1 (clk_slow to clk_fast switch)。当clk_slow上升沿到来,当clk_slow这一路第二级DFF的输出 (sel_sync_slow) 变为0时,并不意味着clk_slow对应的ICG输出立刻变0,而是要等到clk_slow下降沿到来。也就是说此时clk_sw的输出仍旧clk_slow的高电平。这时如果clk_slow第二级DFF输出(sel_sync_slow) 被直接送到clk_fast的选择输入逻辑,如果clk_fast非常快,就会出现clk_slow ICG尚未关断,clk_fast ICG已经打开,造成切换后的输出clk_sw出现毛刺。
在这里插入图片描述
为了避免这样的情况,必须引入第三级DFF。ICG E仍旧用第二级DFF输出,第三级DFF输出送到另一路选择逻辑。 整体来看这样的电路有点浪费资源。

补充4

上面几种电路都只能在时钟正常跑起来的情况下完成切换。如果当前选中的时钟停了,选择信号无法通过由当前时钟驱动的两级DFF,也就无法实现切换了。

时钟停止属于系统级错误,需要在系统设计找到解决方案,这里就不赘述了。

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Hier ist eine mögliche Implementierung des Konstruktors und Destruktors für die Klasse cData: ```c++ class cData { private: double *data; int size; public: cData() : data(nullptr), size(0) {} ~cData() { if (data != nullptr) { delete[] data; } } bool setSize(int newSize) { if (newSize <= 0) { return false; } double *newData = new double[newSize]; if (newData == nullptr) { return false; } // Copy old data into new data int copySize = std::min(size, newSize); // Only copy up to the smaller size for (int i = 0; i < copySize; i++) { newData[i] = data[i]; } // Clean up old data if (data != nullptr) { delete[] data; } // Set new data and size data = newData; size = newSize; return true; } bool setValue(int index, double value) { if (index < 0 || index >= size) { return false; } data[index] = value; return true; } double getValue(int index) { if (index < 0 || index >= size) { return 0.0; // Or throw an exception } return data[index]; } }; ``` Der Konstruktor initialisiert die Datenmitglieder `data` und `size`. Hier wird `data` auf `nullptr` gesetzt, um anzuzeigen, dass der Vektor derzeit leer ist. Der Destruktor prüft, ob `data` nicht `nullptr` ist, um sicherzustellen, dass es etwas gibt, das freigegeben werden muss. Wenn ja, wird das Array mit `delete[]` freigegeben. `setSize()` ändert die Größe des Vektors. Es überprüft, ob die neue Größe gültig ist und ob es genug Speicher gibt, um das neue Array zu erstellen. Wenn ja, wird das alte Array in das neue Array kopiert (nur so viele Elemente wie möglich), das alte Array wird freigegeben und das neue Array wird dem Datenmitglied `data` zugewiesen. `setValue()` setzt den Wert an einer bestimmten Position im Vektor. `getValue()` gibt den Wert an einer bestimmten Position im Vektor zurück. Es gibt 0.0 zurück, wenn der Index ungültig ist (oder Sie können auch eine Ausnahme werfen).

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值