FPGA之道(46)数字电路中的隐患

前言

了解数字电路中的隐患十分有必要,只有对此了如指掌,才能在实际问题中能明白出现问题的原因,而不必误认为是玄学问题。
本文节选自《FPGA之道》,一起站在巨人的肩膀上来看看数字电路中的隐患以及如何应对遇到的隐患。

数字电路中的隐患

数字电路由于其自身的特点,总会伴随着一些隐患。有些时候,这些隐患不会对FPGA设计造成影响,而有些时候,这些隐患却是致命的。为了尽量避免在项目开发时由于这些隐患而导致FPGA设计行为出错,本章节将对这些隐患进行一些简单的介绍。

寄存器输出的不稳定态

不稳定态,指的就是不稳定的状态。请注意,寄存器输出的不稳定态并不是由于赋值冲突而导致的不确定态(即‘X’状态),而是由于不同路径的延迟不一致所导致的数据线上出现了一个或多个非预期的中间状态。
有过时序仿真经历的朋友应该都知道,当寄存器的输出从X变到Y时,中间会有一小段毛刺状态。例如下图中就展示了当8位寄存器的输出从"01111111"变化到"10000000"时,过渡部分出现的不稳定状态,并且如果将不稳定态部分放大,可以看到其实这些看似毛刺的地方实际上也是有着确切的取值的(为了能够看清楚毛刺的全貌,在放大的图中改用十六进制显示数据):
在这里插入图片描述
由于这些毛刺并不是我们想要的,因此它们就构成了数字电路中的一种隐患,因此在本小节,我们将重点讨论这种隐患的成因、影响及应对策略。

单触发器寄存器

如果寄存器只包含一个触发器,即寄存器的存储容量仅为1bit,那么此时是否还会出现不稳定态呢?例如,如果在下一个有效时钟边沿到来时,要将一个1bit位宽的寄存器的输出从逻辑0变到逻辑1,那么波形图是否会出现若干次0、1之间的震荡呢?
事实证明,对于这类寄存器,无论你做多少次时序仿真,它都不会出现不稳定态。原因很简单:对于触发器来说,其输出只会在有效的时钟沿到来时变化,如果其输出变化了N次,那么其至少经历了N个时钟周期(因为如果连续两个时钟周期输入都一样,那么输出不会改变);反之,如果其经历了N个时钟周期,那么其最多也只能变化N次(理由同前)。因此,回到开始的问题,如果在下一个有效时钟边沿到来时,要将一个1bit位宽的寄存器的输出从逻辑0变到逻辑1,那么波形图是不可能出现0、1之间的震荡的,因为一个时钟有效边沿最多只能改变输出一次。

多触发器寄存器

只包含一个触发器的寄存器不会出现输出的不稳定态,那么为什么包含多个触发器的寄存器会出现不稳定态呢?
有很多FPGA的初学者都会对这个问题表示费解。他们认为,无论寄存器的位宽怎样,它们都是由最基本的触发器构成,而触发器在每个时钟的有效沿只能变化一次,因此,除非经历多个时钟周期,否则寄存器的输出怎么可能变化出多个值呢?
“不管你信不信,总之它就是发生了”。事实上,如果你有仔细观察本节最开始的寄存器输出不稳定态示例波形图,你就会发现,虽然在8位寄存器的输出从“01111111”变化到“10000000”的过程中,寄存器输出的不稳定态包含了若干次看似随机的中间值,但实际上任意两个相邻的中间值都是有一定联系的,那就是——它们之间只发生了1bit的变化。 例如0x76、0x36对应二进制表示分别为0b01110110、0b00110110,可见只有dOut[6]发生了变化;进一步分析这些相邻的中间值,还可以发现dOut[6]只在0x76到0x36之间变化了一次,而在其他相邻的中间值中保持不变;再进一步分析还可以发现对于dOut的其他位也是如此。因此,Nbits寄存器中的每个触发器并没有违背触发器在每个时钟的有效沿只能变化一次的原理。
那么到底是什么原因导致多触发器寄存器的输出在一个时钟有效沿后发生了多次变化呢?为了更加清楚的阐述这个问题,我们可以换个角度来观察dOut的时序仿真图,如下即为dOut的按位展开波形图:
在这里插入图片描述
这下我们可以非常明显的看到,造成多触发器寄存器输出出现不稳定态的原因,就是组成寄存器的各个触发器变化不一致造成的。当然了,更为精确的描述应该是——由于线延迟的存在,导致时钟信号到达各个寄存器的时间可能不一样,也导致各个触发器的输出端口到信号接收端所需的时间可能不一样,再加上各个触发器的tco等参数不可能精确的一样,所以当我们改变多触发器寄存器的输出时,就会出现不稳定态。

不稳定态对数字电路的影响

如果寄存器的输出用于产生同步逻辑中另一个或多个寄存器的输入,那么其不稳定态对FPGA设计的影响并不大。因为寄存器仅要求其输入在时钟有效沿时刻稳定即可(其实是在时钟有效沿附近一个比较小的范围内稳定即可),所以只要通过规范时钟信号的使用以及时序分析等手段,确保寄存器时钟有效沿到来的时刻及其需要数据稳定的时间窗口不要落入寄存器出现不稳定态的时间窗口内,就可以保证FPGA设计行为的正确性。

不过对于异步逻辑来说,不稳定态的危害就会凸显出来。
首先,如果一个寄存器的输出用于产生另一个时钟域中寄存器的输入,那么另一个时钟域的有效时钟沿几乎必定会落入不稳定态的窗口中(注意,不是每次时钟有效沿都会采样到不稳定态)。具体的分析请参阅【本篇->编程思路->时钟及时钟域->跨时钟域问题】章节的分析。
其次,如果寄存器的输出用于产生一个电平敏感的信号,那么肯定会导致出错。例如,下图中电路的设计初衷是当计数器的输出b等于3(二进制为11)时对后续电路进行异步复位,不过很可惜,由于寄存器输出不稳定态的存在,导致后续电路在计数器的输出从1变到2(二进制为从01变到10)时,如果计数器的高位稍微先于低位发生变话,就可能发生复位。
在这里插入图片描述
因此,当我们知道了寄存器的输出存在不稳定态后,就要在FPGA设计中杜绝出现上述这两种异步逻辑的情况,否则,隐患就会导致失败。

特定情况下去除不稳定态的方法

难道碰到不稳定态的情况只能小心躲过,绕着它走么?当然不是,对于寄存器输出的不稳定态,我们并不是完全束手无策的,不过能用的上的方法也比较有限,其中最典型的就是使用格雷码,介绍如下:

消除不稳定态的原理

通过对多触发器寄存器的分析,我们知道了产生不稳定态的原因,那就是组成寄存器的各个触发器输出变化时刻的客观不一致性。因此,要想消除不稳定态,就必须消除多触发器输出变化时刻的不一致才行。可是这个世界上没有两个长得一模一样的人,也没有两个一摸一样的触发器,更没有两段精确等长的物理连线,所以,想要通过协调一致多个触发器的输出变化时刻来消除寄存器输出的不稳定态,几乎是不可能的。不过换个思路来看,如果能够让寄存器中的多个触发器每次只有一个的输出会发生变化,那么自然也就不存在变化时刻不一致的问题,从而从根本上杜绝了不稳定态的产生。 例如,当8位寄存器的输出从“10000000”变化到“10000001”时,寄存器的输出就不会出现不稳定态,因为此时仅有寄存器中表示最低位触发器的输出发生了变化。
通过以上分析可以得出,只要能够保证在时钟有效沿时刻,寄存器的输入端与输出端的数据最多只有1个bit不同,就可以消除寄存器输出时的不稳定态。不过这个要求比较苛刻,仍以8位寄存器来说,在任一时刻,如果输出端数据为“00000000”,那么为了消除不稳定态,其输入端的数据只可能有8种选择,即:
“00000001”、
“00000010”、
“00000100”、
“00001000”、
“00010000”、
“00100000”、
“01000000”、
“10000000”;
可是该寄存器输入端的数据可以有256种形式,即从“00000000”~“11111111”。由此可见,如果寄存器的输入与其输出之间没有什么必然联系,是不可能有方法能够消除不稳定态的,请注意,即使寄存器的输入、输出之间有联系,但如果相关性不强,也可能无法消除其不稳定态。所以寄存器输出不稳定态这样一个隐患是无法在FPGA中彻底杜绝的,接下来将要详细介绍的格雷码,也只能够在某些特定应用条件下消除寄存器输出的不稳定态。当然,格雷码的应用并不仅限于此,在后续的章节中本书也会陆续地讲到。

格雷码简介

格雷码,英文全称:Gray code。由于自然二进制码在相邻数据之间可能存在多个bit的变化,例如自然数7和8对应的4bits自然二进制码分别“0111”、“1000”,因此当寄存器的输出从7变到8时,寄存器的每一位都会发生变化,从而造成不稳定态,并且会使得数字电路产生很大的尖峰电流脉冲。而格雷码则没有这一缺点,因为格雷码是一种数字排序系统,其中的所有相邻整数在它们的二进制表示中仅有一位不同。例如下表给出了4bits自然二进制码、格雷码与十进制整数的对照表:

十进制数 自然二进制编码 格雷码
0 0000 0000
1 0001 0001
2 0010 0011
3 0011 0010
4 0100 0110
5 0101 0111
6 0110 0101
7 0111 0100
8 1000 1100
9 1001 1101
10 1010 1111
11 1011 1110
12 1100 1010
13 1101 1011
14 1110 1001
15 1111 1000

从上表我们看出,格雷码在任意两个相邻的数之间转换时,只有1个bit发生了变化,所以它有效的避免了寄存器由一个数值到下一个数值时的不稳定态。并且由于格雷码中最大数与最小数之间也仅1个bit不同,因此通常又被称作循环二进制码或者反射二进制码。
不过格雷码也有一个缺点,那就是相比于自然二进制码来说,它是一种无权码,因此很难直接进行比较和数学运算,所以一般都需要将采集到的以格雷码为表示形式的数据先转换成自然二进制码,然后再参与运算。因此接下来我们将介绍格雷码与自然二进制码之间的转换方法。

常用的格雷码编、解码方法

我们一般采用以下方法对数据进行格雷码的编解码:

从自然二进制码到格雷码

该过程也称为格雷码的编码,方法是从二进制码的最右边一位(最低位)起,依次将每一位与左边一位进行异或运算,作为对应格雷码该位的值,而最左边一位(最高位)不变。 对应公式如下:
g[n] = b[n],
g[i] = b[i] xor b[i+1] (i∈N,n-1≥i≥0);
其中g、b分别对应n位的格雷码和二进制码。
例如,将自然二进制码“10110”转换为格雷码,可以形象的用下图表示其转换过程:
在这里插入图片描述

从格雷码到自然二进制码

该过程也称为格雷码的解码,方法是从格雷码左边第二位(次高位)起,将每一位与其左边一位解码后的值异或,作为该位解码后的值,而最左边一位(最高位)的解码结果就是它本身。对应公式如下:
b[n] = g[n],
b[i] = g[i] xor b[i+1] (i∈N, n-1≥i≥0)。
其中g、b分别对应n位的格雷码和二进制码。
例如,将格雷码“11101”转换为自然二进制码,可以形象的用下图表示其转换过程:
在这里插入图片描述

从卡诺图看格雷码编码的非唯一性

格雷码其实并不唯一,这点可以从卡诺图上看出。例如对于2位宽的寄存器b,如果将b[1]、b[0]分别看做卡诺图的两个变量的话,并用b对应的自然二进制数值来填充卡诺图,可作出卡诺图如下:

b b[0] = 0 b[0] = 1
b[1] = 0 0 1
b[1] = 1 2 3

我们可以知道,在卡诺图中,“相邻”(不仅仅是空间相邻)的小方块之间,只有一个变量发生了变化。这也就是说,从上述卡诺图中任意一个方格出发,每次仅能移动到其相邻的、未被遍历的方格中,如此往复,直到再次回到出发点时,能够将卡诺图中除出发点外所有的小方格遍历且仅遍历一遍,那么沿着这条路线就可以得到一组格雷码。
对于上述卡诺图,如果出发点选择在1,沿顺时针方向走一圈,可以得到1、3、2、0,将其转化为二进制编码,为01、11、10、00,可见是一组格雷码;如果出发点选择在2,逆时针方向走一圈,可以得到2、3、1、0,将其转换为二进制编码,为10、11、01、00,可见仍是一组格雷码。按照如上思路,我们还可以得到更多的格雷码分组。如果对于更多触发器组成的寄存器,其可得到的格雷码组数就更加多样了。
由此可见,格雷码并不唯一,不过建议大家还是采用前面小节介绍的常用的格雷码编、解码方法,因为这种方法能够更加方便我们在格雷码和自然二进制码之间进行转化,否则我们可能需要建立和传送专门的查找表来进行格雷码的编、解码工作。
但是,利用卡诺图,我们可以得到一些状态空间不是2的整数次幂的格雷码编码集合,而这是【常用的格雷码编、解码方法】小节中的公式所不能解决的。其基本思路为:按照相邻格移动规则,当再次回到出发点时,故意让一些小方格没有被遍历到。例如对于3位宽的寄存器c,如果将c[2]、c[1]、c[0]分别看做卡诺图的三个变量的话,并用c对应的自然二进制数值来填充卡诺图,可作出卡诺图如下:
3输入	c[0]=0c[1]=0	c[0]=0c[1]=1	c[0]=1c[1]=1	c[0]=1c[1]=0c[2]=0	0	2	3	1c[2]=1	4	6	7	5
对于上述卡诺图,如果遍历环路为2、3、1、5、7、6(最后再回到2),将其转化为二进制编码,为010、011、001、101、111、110,可见这是一组格雷码,并且是一组元素为6的格雷码。此时,如果你需要实现一个6进制的计数器,采用这组格雷码就能保证任何时候计数器的输出在外界看来都只有一bit的翻转。但是请注意,采用上述方法虽然可以得到一些非2的整数次幂的格雷码编码集合,但这并不意味着你可以得到任意元素数的格雷码编码集合,例如,你无法得到只含有3个元素的格雷码集合。

采用格雷码消除不稳定态

了解了格雷码的概念和特性后,我们就可以利用格雷码来消除一些特定情况下的不稳定态。就拿在【不稳定态对数字电路的影响】小节给出的那个可能会提早给出复位行为的电路为例,如果我们将原先的自然二进制码计数器改为格雷码计数器,并在其输出等于格雷码的“3”时给出后续电路的复位信号,则复位行为就不会出现任何问题。修改后的电路如下:
在这里插入图片描述
格雷码的应用地方还有很多,在后续的章节中我们还会继续介绍,例如【本篇->编程思路->数据的存储->异步FIFO的HDL描述与用法】小节中,就利用了格雷码来保证跨时钟域读、写地址传递的稳定性,并对格雷码的应用做了更进一步的探讨。

发布了806 篇原创文章 · 获赞 1541 · 访问量 151万+

猜你喜欢

转载自blog.csdn.net/Reborn_Lee/article/details/104342392