欢迎光临散文网 会员登陆 & 注册

【逻辑门的奇妙冒险】第5篇 时序逻辑——带上时间轴,我们去未来

2023-03-13 01:43 作者:-喵客信条-  | 我要投稿

本篇目标:

1.搭建并分析SR锁存器;

2.深入理解SR锁存器的原理;

3.基于SR锁存器,搭建D锁存器;

4.理解时钟与波形;

5.基于D锁存器,搭建D触发器;

6.基于D触发器,搭建寄存器;

7.利用寄存器,实现一个计数器;

8.搭建寄存器文件

 

上一篇我们介绍了组合逻辑电路,它们都由与或非三种逻辑门搭建,然后层层封装层层套娃,最终搭建出来一个功能相对丰富的算术逻辑单元ALU。ALU可以根据输入的选择信号S,来决定给出俩输入AB的不同运算结果。这个模块可以说很有意思了,但是包括这个模块在内的所有组合电路,都有一个特点:就是此刻的输出信号完完全全只与此刻的输入信号有关,与过往的输入不相关,也就是说,组合逻辑电路,没有历史信息。某某器件被输入0101,就得到1010——这一对输入输出就会永远成立,不论这个器件之前被输入什么,完全无所谓,但凡是0101来了,永远得到1010——它没有“记忆”。

 

但是细细琢磨也没毛病呀,它们都是用与或非逻辑门搭建起来的。逻辑门这么简洁的东西,本来就没有记忆呀(摊手.jpg)。嘿嘿,闲话不多说,开始本篇的奇妙冒险吧~

 

1.搭建并分析SR锁存器

开门见山,关于这个器件,我先隐藏一下需求和设计思路,直接上这个电路,马上开始分析。试图为读者复现作者当初学习这部分知识的那种“哇好有意思”和“哦原来如此”的感觉。这个电路很简单,只用或门、非门各两个,长这样:

诶,这个电路和我们以前见过的不一样哦,怎么有一个回环呢?有意思哦。我们先分析一下试试,如果俩输入SR都是11,那么A点就直接是1了,B点是0,B点同时输入到下面那个或门,得到C点是1,D点就是0,同时给到上面那个或门,使得A点是1,然后B点是0,C是1,D是0……虽然层层转圈分析不尽,略有小奇怪,但是确实也没啥问题吧?我们可以说,输入SR是11,输出Output就是0。如果要给它画一个真值表的话,那么这一项有咯。

 

我们继续看不同的输入是怎么样:

左上角:输入SR分别是01,那么首先C点是1,D点是0,进而A点就是0,B点是0,C点是1……OK,稳定循环了,我们可以表达为:SR=01,Output=1。

右上角:输入SR分别是10,那么首先A点是1,B点是0,进而C点就是0,D点是1,A点是1……OK,稳定循环了,我们可以表达为:

SR=10,Output=0。

左下角:输入SR分别是00,那么假设D点是1,A点就得到1,B点是0,进而C点是0,D点是1,A点是1……OK,稳定循环了,也即:SR=00,Output=0。

右下角:输入SR分别是00,那么假设D点是0,A点就得到0,B点是1,进而C点是1,D点是0,A点是0……OK,稳定循环了,也即:SR=00,Output=1。

 

等等等等?!左下角和右下角的图,输入SR都是00,输出却不一样?!再好好看看分析过程,好像也没错呀。我们再看一眼,再进一步整理一下:

左下角的SR=00可以看做是从左上角的SR=01,将R变成0得到,同时内部的ABCD点状态还是那个样子不变;

左下角的SR=00可以看做是从右上角的SR=10,将S变成0得到,同时内部的ABCD点状态还是那个样子不变。

 

再进一步,我们可以得到这样一个图:

我们知道电路上的输入要从SR=11变到SR=00,有两条路径,要么先让S等于0,然后R,要么就是先让R等于0,然后S。有意思的是,两种路径最后的输出不一样诶——输出Output和以往的路径信息有关诶,这个神奇的Output仿佛记得自己是从哪条路下来的——诶,这个电路有“记忆”!


2.深入理解SR锁存器的原理

为啥SR锁存器这个电路结果会有记忆的神奇功能呢?它的原理到底是啥呢?好怪哦,再看一眼:

我们发现俩或门好像有点摸鱼,如果我们大胆一点,化简一下电路,将AD两点直连,将BC两点直连,同时把或门去掉。诶嘿,好像没毛病诶:

电路也是稳定的!其实呢,这个结构就是“双稳态电路”,说白了就是两个非门头尾相接,我们再上导线捋一下就看得更清楚了:

所谓的记忆功能,其实就是这个结构在发挥作用,这也是上一篇的组合逻辑从未有过的结构,很有意思吧。

 

进一步地,我们不难发现,这个结构下的AD点和BC点必然是相反的,毕竟是通过非门连接嘛。于是我们就可以利用这一点,来定义这个结构存储的信息了,AD点和BC都往外输出,并且改名Q和~Q:

但是呢,聪明的小伙伴发现了一个问题,Q点和~Q点对应回原来的结构分别是B点和D点,未必是相反的关系呀,例如:

SR=11的时候,BD都是0,Q和~Q都是0,这就有点不对劲了。Q和~Q的正确状态应该是这样的才对:

事实上,S的意思是Set,R的意思的Reset,也就是说:SR=10,就是要设置Q为1;SR=01,就是要重置Q为0,SR=00;就是不设置不重置,Q点保持原值;SR=11,既要设置也要重置,Q点表示有点矛盾……于是我们整理出SR锁存器的真值表:


3.基于SR锁存器,搭建D锁存器

我们现在知道了SR锁存器的结构、特性和原理了,但是这玩意多少差点意思——因为SR同时是1的时候,Q和~Q都是0,又因为Q和~Q原本是非门环的两端,理应相反才对,现在被破坏了,不成环了,就很尴尬。我们有没有办法把这个状态取消掉呢?

 

诶,或许我们套娃一层,整一个新的壳,把壳的SR输入映射到真SR锁存器输入,主要目标是避免SR=11的非法状态:

看起来可以哦,来,上套路,填真值表(顺手给NewS和NewR这俩草率的信号名换一个雅称):

写表达式:

S = E & D

R = E & ~D

然后上电路:

效果怎么样呢?我们来测试一下:



SR锁存器的输入永远不会是11,并且我们观察电路可以发现:

当E=1时,若D=1,则SR=10,Set状态,Q为1,即Q=D;

当E=1时,若D=0,则SR=01,Reset状态,Q为0,即Q=D;

当E=0时,无论D何值,SR都是00,Lock状态,Q保持原值

 

诶这个性质真漂亮诶,电路的“记忆”功能看起来有点样子了。顺便解密一下小彩蛋,E是Enable,D是Data。哦吼,难怪E=1,Q=D,因为enable嘛,而E=0,Q不变呢,因为disable嘛~


4.理解波形与时钟

逻辑门的奇妙冒险走到了这里,就已经有了神奇的“记忆”功能。也就是说,从现在开始,逻辑门有了“时间轴”,有了过去和未来。于是,很自然的,时序逻辑电路就比较少用真值表了,毕竟真值表是静态的,不带时间信息,却而代之的是带有时间信息的波形图,例如刚刚介绍的D锁存器,它的波形图就是这样的:

我们一般约定,波形上凸的表示1,下凹的表示0(如果是多比特信号,就直接把数值标在脸上)。我们分析就时候就像上图的标记那样:红框里面就表示E=1的时候,Q就等于D,红款之外,就是E=0,Q等于先前的Q,保持不变。

 

再进一步地,既然有了波形,有了过去未来,那是不是应该有一个时钟信号呀,它每10ns变化一轮,告诉所有时序电路:“嘿嘿,过了10ns了,干活干活咯”。

实际上,这个信号是由一个叫做晶振的装置产生的,它规律地反复横跳,给所有时序电路做同步。如果读者对晶振的原理感兴趣,可以自己去搜索相关资料(我不会,顶锅盖跑)。这里手动画一个重点,时钟信号clock是0还1,其实并不重要,重要的是clock从0到1的变化,叫做“上升沿”(posedge)。同样的也有从1到0的变化,下降沿,negedge。另外,clock有时候也简写为clk。

就在我们的小软件Logisim里面,有一个方便的时钟信号,在选项Simulate这里可以启用时钟,调整时钟频率等,大家可以先玩玩:

我们接下来针对时序逻辑电路的分析就要看波形,关注时钟了~

 

5.基于D锁存器,搭建D触发器

上文已经介绍过,从SR锁存器开始,基于非门环,或者专业点说“双稳态电路”,我们的电路开始有了“记忆”功能。但是SR锁存器可能存在一个非法的、可能破坏非门环的状态,我们通过对SR锁存器套一层封装,成功地避开了这个状态。事实上,到了D锁存器这里,我们就很好地实现了电路的“记忆”功能。

 

但是呢,D锁存器还是差点意思:我们希望用clk信号来控制输入,如果直接将clk接到D锁存器的输入E,然后输入D接到一个数据源上,我们期待锁存器可以保持上一次数据源的信息。然而,这个数据源也是听clk信号指挥的,也就是每一个周期给一次新值。这样一来,D锁存器的波形就是这样了:

啊这,Q和source完全相等的话,不就跟导线一样了,这个锁存器就废了呀。我们的期待是锁存器可以保持上一次数据源的信息,波形应该是这样的才对:

都说要记忆呀,要历史呀,那这样的波形才对劲嘛。这就要求我们的器件必须在时钟从0到1的上升沿这个跳变的瞬间,完成存储,因为source也马上就要变了,如果存储慢了,source的历史信息就丢了,所以必须要快。

 

那具体要怎么做呢?一个可行的方案是这样的:

有两个D锁存器,其中一个的输入D是另一个的输出Q,而且,其中一个的E端口和clk信号直连,另一个的E端口和clk信号的取反连接。这样一来,两个D锁存器总是处于一开一关的状态,不会同时打开也不会同时关闭。这样的电路会怎么工作呢?不妨直接试一试:

我们注意到,当clk等于0的时候,前一个锁存器打开,D直达temp处,后一个锁存器关闭,不管;当clk跳变成1的瞬间,前一个锁存器关闭,temp的值被锁住,同时后一个锁存器打开,temp的值被第二个锁存器读入,完成存储;当clk回到0的时候,前一个器件打开,后一个关闭,于是,上一个D的值就被记下来,放在第二个锁存器里面了。很有意思呀,这个简单巧妙的结构实现了我们的预期,我们封装一下,用接上真正周期性变化的clk信号:


这个东西就叫做D 触发器,它可以记录D信息上一个时钟周期的信息。诶,我们是不是再带劲一点,来个套娃,既然可以记录上一个时钟的信息,也就可以记录上上个时钟的,还可以记录上上上个时钟的……于是我们就得到了这样的东西:

它的波形也很有意思,一层一层套娃:


6.基于D触发器,搭建寄存器

这个D触发器呀,就两个输入,一个数据源D输入,一个时钟输入。这样它的功能就稍微有些不完备,根据我们的现实生活经验,我们至少需要一个开关,一个复位信号。开关信息我们叫做enable,复位就是reset。注意这里的enable和D锁存器的enable不是同一个哦。如果能加上这两个信号,那么我们的记忆器件就更加完备了,到了这一步,一般我们称之为寄存器(register)。

 

那么寄存器的enable信号要怎么实现呢?我们注意到,D触发器的时钟如果不变,那么整个器件就也就不会变化了,毕竟奥义之时间暂停嘛(这句划掉)。所以,如果我们要让enable信号来控制这个器件,我们可以让enable信号来决定是否授予时钟信号,电路实现起来也简单,也就是将enable信号与时钟进行与运算:

显然,当enable等于0的时候,D触发器就收不到时钟了,enable等于1才可以。OK,简单地实现了。那reset呢?我们希望reset信号为1的时候,D触发器被重置为0。一个可行的方案是这样的:

简单分析一下可以发现,reset等于1时,clk可以被正常连通,同时输入的源被改成了0,也就成功完成了预期的目标。我们最后封装一下,就是长这样啦:


进一步地,现在这个器件只能存储1比特的信息诶,如果要存储多比特呢?一个方案是直接并行:

这就是一个4比特的寄存器了,要多少比特都可以这样并行。甚至可以相似结构再套一层:

这就是16比特的寄存器了,下面是封装后的样子:

(另外,我原本想着所有器件都由自己用与或非三门搭建出来,理论上确实是可以的,没有问题。但是我们用的这个小软件Logisim,如果全部用自己的模块,它有点扛不住,会卡死,所以后续我们一些规模比较大的寄存器组,就用Logisim软件提供的模块吧,这样软件会比较流畅。)


7.利用寄存器,实现一个计数器

到了这里,我们就有了一个具有记忆能力的,同时功能也比较完备的器件,我们可以把它和上一篇的组合逻辑电路结合一下,整点小活。首先,最直观的应用就是用寄存器和加法器搭建一个计数器:



寄存器的输入会进入一个加法器,和常量1相加的结果再输入回寄存器,这样一来,寄存器就可以不停地累加1了。很自然地,我们就会想到,如果加法器的另一个输入是常量2,那就可以不停地累加2了。输入常量n,那就不停累加n。

 

但是现在的这个累加,也就只能是0大15循环,假如我们想要0到9循环呢?我们注意到reset信号可以用起来,如果我们能在寄存器的值等于9的时候,令reset等于1就可以了,寄存器就会重置。好勒,思路有了,马上开工:

那如果我们想要倒计时呢?也简单,那加法器换成减法器,每次减掉1,并且相等判断检测到0的时候,就不管减法器的运算结果,将寄存器的输入重新设置为9就可以了,reset暂时不好用,那我们就直接在输入端用复用器好了:



那如果我们更飘逸一点呢,要求电路按顺序给出一个任意序列呢,例如2、3、5、7、11、13……一个简单的方案是直接用计数器的输入,当做复用器的选择信号,复用器的输入,我们以前被要输出的序列准备好即可:


使用这个电路结构,我们就可以让电路给出任何序列了~

 

8.搭建寄存器文件

最后一关,我们希望让寄存器们合作一下,组合出一个功能更加完备的模块。最直观的要求就是寄存器有多个,我们可以把某一个特定的值写入其中任意一个寄存器,我们也可以读出任意一个寄存器的值。

 

首先,我们先实现读的功能。先带大伙琢磨一下:假设寄存器有8个,我们要读出其中任意一个寄存器的值,就应该给一个3比特的读地址,告诉我们的电路,到底要读出哪一个寄存器,也就是说,这个功能涉及两个端口,输入raddr和输出rdata,分别是读地址和读数据。

 

具体的实现也比较简单,直接把所有的寄存器的值都接入到一个大复用器的输入,然后读地址raddr作为复用器的选择信号,复用器的输出就是读数据rdata。再进一步地,我们可以要求读端口有两套,也就是两个raddr和两个rdata,分别用两个大复用器实现:



接下来我们要实现一下写的功能,再带大伙琢磨一下:总不能每次写入都全部写进去吧,那所有寄存器都没有区别了,寄存器堆就退化成一个寄存器了……所以,我们写入的时候,需要多一个信号,写地址waddr,告诉我们的电路,哪一个寄存器要被写入,其他的寄存器保持不变。同时我们也不能时时刻刻都往里面写东西吧,总得休息一会吧。所以我们还需要一个写使能信号wen,当且仅当wen等于1的时候,电路才会写入,否则就不变。最后很自然地,我们还需要告诉电路要写入什么数据,也就是写数据wdata。OK,小结一下,我们有仨输入,一个总使能wen,一个写地址waddr,一个写数据wdata。

 

具体的实现呢?这仨输入的电路,要怎么连呢?我们可以这样操作:waddr所指向哪一个寄存器,我们就把那个寄存器的wen连上,其他的寄存器都把使能信号都关掉,电路可以是这样的:


这八个输入分别接到八个寄存器的使能信号即可,我们知道waddr

等于何值,对应的行就会点亮,允许wen通过,实现了我们的预期。而写入功能的第三个信号wdata就比较简单了,有了wen和wadd的控制,wdata可以直接连接到所有寄存器的输入端。最后我们的寄存器文件就是这样的:

这里有一个小彩蛋:我们直接将0号寄存器锁定成0,它不可写入,永远都只会读出0,电路上可以直接把0号寄存器换成常量:

至于为什么要这样做呢?因为它的存在会方便很多伪指令。诶这又是啥意思呢?这里先留个坑,把它说明白需要一些前置知识,咱们以后再细说。最后我们拉出八个调试接口,方便我们在外面检查寄存器的值是否符合预期,最后封装好就是这样。一般我们管它叫寄存器文件,RegFile:




扩展阅读:

(建立时间与保持时间,鸽了)


【逻辑门的奇妙冒险】第5篇 时序逻辑——带上时间轴,我们去未来的评论 (共 条)

分享到微博请遵守国家法律