0%

MIT 6.004:L05 时序逻辑

整理自 MIT OCW 6.004 Computation Structures(Spring 2017)L05 注解幻灯片。

源网页:5.1 Annotated Slides | Sequential Logic

讲师:Chris Terman。图片直接引用 OCW 原站链接。

L05:时序逻辑(Sequential Logic)

本讲在组合逻辑之上引入状态(state)与存储(memory):从电容存储与反馈双稳态,到 D 锁存器、边沿触发 D 寄存器,再到单时钟同步电路的建立/保持时间与 tCLKt_{\mathrm{CLK}} 约束。

1. 还不能造的东西(Something We Can’t Build (Yet))

Something We Can't Build Yet

上一讲学会了按功能规格做组合逻辑:输出只由当前输入决定。

但有一类简单器件做不到:灯作输出、按钮作输入——灯灭时按一下灯亮,灯亮时再按一下灯灭。输出并不只是当前输入的函数:第奇数次按下亮、偶数次灭,器件在“记住”上次是奇次还是偶次。这种依赖输入历史的器件称为有状态

更细微的一点:关心的是按钮从松开到按下的跳变时刻,而不只是“当前是否按下”。内部状态使相同输入可产生不同输出;纯组合器件做不到。本讲要把状态写进电路。

2. 数字状态:想造什么(Digital State: What We’d Like to Build)

Digital State What We'd Like to Build

引入存储器抽象:用一个或多个比特编码系统当前状态,这些比特作为数字值出现在输出(图中 “Current State”)。

当前状态与当前输入一起进入一块组合逻辑,产生两组输出:

  • 下一状态(next state):比特数与当前状态相同
  • 系统输出:对外可见的信号

组合逻辑的功能规格(真值表或布尔方程)规定:下一状态与系统输出如何由当前状态与当前输入决定。

存储器有两个输入:LOAD 控制何时用下一状态替换当前状态,以及给出下一状态数据的输入。周期性触发 LOAD,就得到当前状态序列;序列中每一态由上一态与触发时刻的输入决定。

含组合逻辑与存储器的电路称为时序逻辑(sequential logic)。若存储器存 KK 比特,可能状态数至多为 2K2^K。本章讨论如何造可加载的存储元件;下一章讨论如何系统化设计时序行为。

3. 用电容做存储(Memory: Using Capacitors)

Memory Using Capacitors

比特已用电压表示,可考虑用电容存某电压。电容是无源二端器件:两平行板隔绝缘体,电荷 QQ 与电压差 VV 满足 Q=CVQ=CV。接到较高电压叫充电,接到较低电压叫放电

电容存储单元示意:电容一端接稳定参考;另一端经 NFET 开关接到 bit line;开关栅极接 word line

  • :把 bit line 驱动到目标电压(数字 0/1),再令 word line 为 HIGH,电容充/放到与 bit line 同压;再令 word line 为 LOW,关断开关,理想情况下电荷可一直留在内部极板。

  • :先把 bit line 充到中间电压,再开 word line,使 bit line 与存储电容电荷共享。存 1(较高压)时电荷流向 bit line,电压略升;存 0 则略降。变化通常很小,需灵敏放大器(sense amp)检出并还原成合法数字电压。

  • 优点:存储电容极小,现代 IC 可在廉价 DRAM(dynamic random-access memory)上集成数十亿比特,单位比特成本低。

  • 缺点:读写需要复杂的操作,所以访问慢;必须注意在外部电噪声干扰下,小心地保持储能电容器上的电荷;NFET 关断仍有泄漏,当前技术下须约每 10 ms 刷新(refresh)——读出再写回。所以我们需要搭配精心设计的模拟电子电路(analog electronics) [1]

或许我们可以通过设计一种利用“反馈”持续刷新存储信息的电路来克服电容式存储的缺点。

4. 用反馈做存储(Memory: Using Feedback)

Memory Using Feedback

两只组合反相器接成正反馈环:一输入为数字 0 → 其输出为 1 → 另一反相器输出为 0,又接回原输入。只要接电源与地,该配置在噪声下仍可保持;两线数字值对调也是稳定的。有两种稳定配置,称为双稳态(bi-stable)存储元件。

把两反相器看成一个系统,其 VTC 给出 VOUTV_{\mathrm{OUT}}VINV_{\mathrm{IN}} 关系;输出接回输入又要求 VIN=VOUTV_{\mathrm{IN}}=V_{\mathrm{OUT}}。图解求交点:两端交点为稳定——VINV_{\mathrm{IN}} 小扰动几乎不影响 VOUTV_{\mathrm{OUT}},系统会回到稳态。中间交点为亚稳态(metastable):理论上可无限停在该电压,但极小扰动就会迅速滑向某一稳态。作存储用时须避免进入亚稳态(下章再谈)。

5. 可置位存储元件(Settable Memory Element)

Settable Memory Element

2 选 1 MUX 做可置位存储:MUX 输出作状态输出 QQ,并回接到 MUX 的 D0D_0D1D_1 作数据输入;选择端作加载信号(此处称 gateGG)。

  • G=LOWG=\mathrm{LOW}:输出经 D0D_0 回环,形成上节的双稳态正反馈;电路出现环路,不再是组合电路。
  • G=HIGHG=\mathrm{HIGH}:输出跟随 D1D_1(数据输入)。

加载:将 GG 置 HIGH 足够久,使 QQ 有效且稳定;真值表上 G=1G=1QQ 跟随 DD,变化时序由 MUX 的 tPDt_{\mathrm{PD}} 决定。再把 GG 置 LOW,进入“记忆模式”,正反馈无限保持稳定 QQ

6. 新器件:D 锁存器(New Device: D Latch)

New Device D Latch

该存储器称为 D 锁存器(D latch),原理图符号如图。

  • 门控 GG 为 HIGH:打开(open),信息从 DD 流向 QQ
  • GG 为 LOW:关闭(closed),进入记忆模式,记住 GG 由 HIGH→LOW 时 DD 上的值

右侧时序图:稳定段为恒定 LOW/HIGH,变化段画成多次跳变。GG 为 HIGH 时,DD 稳定后至多再经 tPDt_{\mathrm{PD}}QQ 到达新稳定值。

理论期望:GG 变 LOW 后,QQ 保持跳变瞬间 DD 上的值。但一般组合器件在输入跳变后,tCDt_{\mathrm{CD}}tPDt_{\mathrm{PD}} 之间输出可任意变化——若 GG 的 1→0 使 QQ 短暂失效,要记的正是 QQ 上的值!因此必须保证 GG 的 1→0 不影响 QQ[2]

7. 请求宽容(A Plea for Lenience)

A Plea for Lenience

因此,存储元件选用的是宽容的(lenient)MUX;宽容 MUX 的真值表见上图。在下列任一条件下,即使输入发生跳变,输出仍保持有效且稳定:

  1. GG 置 HIGH 以加载锁存器时:一旦 DD 已有效稳定满 tPDt_{\mathrm{PD}},即可保证 QQ 稳定有效,且取值与 DD 相同,与 QQ 的初值无关。
  2. QQDD 均已有效稳定满 tPDt_{\mathrm{PD}},则此后 GG 上的跳变不影响 QQ——正是靠这一点,GG 才能做 1→0 而不污染 QQ
  3. GG 为 LOW,且 QQ 已稳定至少 tPDt_{\mathrm{PD}},则此后 DD 上的跳变不影响输出。

宽容性是否足以保证锁存器正常工作?还不够——除非我们小心保证信号在正确时刻稳定,才能用上 MUX 的宽容行为。

8. 还须一点纪律(… With a Little Discipline)

With a Little Discipline

保证锁存器按预期工作,需按下列步骤操作:

  1. 先在 GG 为 HIGH 时,把待存值放到 DD;经 tPDt_{\mathrm{PD}} 后,即可保证该值在 QQ 上稳定有效。这就是上节条件 1。

  2. 再等一个 tPDt_{\mathrm{PD}},让 QQ' 输入上新值的信息传遍锁存器内部电路。此时 DD QQ' 都已稳定至少 tPDt_{\mathrm{PD}},从而满足上节条件 2。
    因此若 DD 稳定满 2tPD2\,t_{\mathrm{PD}}GG 上的跳变就不会影响 QQ。对 DD 的这一要求称为锁存器的建立时间(setup time):即 GG 做 HIGH→LOW 之前,DD 必须已稳定有效多久。

  3. 这时可将 GG 置 LOW,同时仍保持 DD 稳定有效。再经一个 tPDt_{\mathrm{PD}},让新的 GG 值传遍内部电路后,就满足了上节条件 3,此后 DD 上的跳变不再影响 QQ
    DD 稳定性的这一进一步要求称为锁存器的保持时间(hold time):即 GG 跳变之后,DD 还必须继续稳定有效多久。

时序参数 英文名称 概念定义 针对的对象(作用接口与角色)
建立时间 (tSETUPt_{\mathrm{SETUP}}) Setup Time 时钟沿到达之前,数据必须保持稳定不变的最短时间。 输入端 DD(外部输入信号需满足的约束/条件)
保持时间 (tHOLDt_{\mathrm{HOLD}}) Hold Time 时钟沿到达之后,数据仍需继续保持稳定不变的最短时间。 输入端 DD(外部输入信号需满足的约束/条件)
传播延迟 (tPDt_{\mathrm{PD}}) Propagation Delay 从时钟触发沿到达起,到输出彻底稳定到最终正确逻辑状态所需的最长时间。 输出端 QQ(寄存器自身产生的物理延迟/表现)
污染延迟 (tCDt_{\mathrm{CD}}) Contamination Delay 从时钟触发沿到达起,到输出开始发生改变/受到影响所需的最短时间。 输出端 QQ(寄存器自身产生的物理延迟/表现)

建立时间与保持时间合称动态纪律(dynamic discipline),锁存器要正确工作就必须遵守。

简言之,动态纪律要求:在 GG 发生跳变的前后DD 都必须稳定有效。只要电路设计遵守动态纪律,就能保证门控做 HIGH→LOW 时,该存储元件可靠地存下 DD 上的信息。

9. 试一把(Let’s Try it Out!)

Let's Try it Out

把锁存器用作时序系统的存储:打开锁存器(GG HIGH),让新状态传到 QQ(当前状态),再经组合逻辑更新下一状态。问题:GG 若 HIGH 过久,系统形成环路,新状态沿环快速变化,加载计划失败。

因此 GG 为 HIGH 的窗口既要够长以满足动态纪律,又要够短,使锁存器在新状态绕环一圈前重新关闭。精确间隔几乎无法保证——只有时序上下界,没有精确间隔。需要的是标记某一瞬间的加载信号,而不是一段时间。

10. 不可靠的控制系统(Flakey Control Systems)

Flakey Control Systems

类比:收费站闸门前排队的车 = 时序逻辑中的状态序列;闸门 = 锁存器。闸门关闭时车等候;打开后第一辆驶出——但何时关闸很难:开得够久让第一辆过,又不能太久让后面的车也过。与单锁存器作存储时的问题相同。

11. 解法:擒纵策略(双闸)(Solution: Escapement Strategy (2 Gates))

Escapement Strategy Two Gates

两道闸:起初 Gate 1 开、Gate 2 关,恰允许一辆进入;某一瞬间关 Gate 1、开 Gate 2,让站内那辆驶出,并挡住后续车辆。重复两步,逐辆处理。关键:任意时刻不存在同时穿过两道闸的通路

这与机械钟表的擒纵机构(escapement)相同:保证发条齿轮一次只进一齿,防止整天一下子转完。观察输出:Gate 2 每次打开后不久驶出一辆;车辆按 Gate 2 打开间隔通过。把该思路用于时序逻辑的存储元件。

12. 边沿触发 D 寄存器(Edge-triggered D Register)

Edge-triggered D Register

用两个背靠背锁存器做 D 寄存器(D register):加载信号通常叫时钟(clock);DD/QQ 角色与锁存器相同。

从外部看,DD主锁存器(master),QQ从锁存器(slave)。时钟在进主锁存器前反相:主开则从关,反之亦然——任意时刻没有从寄存器 DDQQ 的通路。

时钟反相器延迟可能会引发担心:上升沿时是否会有短暂两 Gate 都 HIGH?我们可以换用“GG LOW 时打开、GG HIGH 时关闭”类型的主锁存器,往往不必另加反相器。

因锁存器内正反馈双稳态(具有两个可以长期保持的稳定工作状态),寄存器也常被称为 flip-flop (触发器)。

13. D 寄存器波形(D-Register Waveforms)

D-Register Waveforms

整体行为简单:时钟 0→1 上升沿采样 DD,并保持到下一上升沿;QQ 即寄存器中存的值。

时钟 LOW→HIGH 为上升沿,HIGH→LOW 为下降沿。主锁存器输出记为 STAR:

  • 上升沿:主由开→关,采样输入并进入记忆;时钟为 HIGH 期间 STAR 保持稳定。
  • 下降沿:主打开,STAR 再跟随 DD(延迟为锁存器 tPDt_{\mathrm{PD}})。

从锁存器:上升沿时打开,输出跟随 STAR;但时钟 HIGH 时主已关,STAR 稳定,故 QQ 在可能的一次跳变后也稳定。下降沿时从由开→关,采样 STAR;时钟 LOW 期间 QQ 保持稳定。

单独看 QQ:只在上升沿从锁存器打开时变化,故称正边沿触发 D 寄存器(positive-edge-triggered D register)。原理图时钟端用小三角标记。

14. 关于那个保持时间…(Um, About That Hold Time…)

About That Hold Time

下降沿时从锁存器由开→关,其输入 STAR 必须满足从锁存器的建立/保持时间。麻烦在于:主同时打开,STAR 可能在时钟边沿后很快变化。主的污染延迟 tCDt_{\mathrm{CD}} 告诉我们其输出旧值在下降沿后还能稳多久;从的保持时间告诉我们边沿后其输入还须稳多久。

正确工作要求:主的 tCDt_{\mathrm{CD}} \ge 从的保持时间。分析须考虑工艺偏差、温度、电源等。必要时可在主从之间加额外门延迟(如两级反相器)增大相对下降沿的污染延迟。从锁存器保持时间问题只能靠改电路设计解决。

15. D 寄存器时序(D-Register Timing)

D-Register Timing

D 寄存器时序规格小结:

  • tPDt_{\mathrm{PD}}:上升沿后,QQ 变为有效稳定的上界
  • tCDt_{\mathrm{CD}}:上升沿后,旧 QQ 仍保持有效的下界

二者均相对时钟上升沿度量。寄存器设计为宽容:若新旧 QQ 相同,上升沿期间 QQ 稳定性有保证——tCDt_{\mathrm{CD}}/tPDt_{\mathrm{PD}} 仅在 QQ 实际变化时适用。

为保证主锁存器正确,DD 须满足主的建立/保持:

  • tSETUPt_{\mathrm{SETUP}}:上升沿 DD 须有效稳定多久
  • tHOLDt_{\mathrm{HOLD}}:上升沿 DD 须再保持多久

使用厂商库中的 D 寄存器时,要从数据手册查这四个参数,再分析整电路时序。

16. 单时钟同步电路(Single-clock Synchronous Circuits)

Single-clock Synchronous Circuits

6.004 中使用寄存器的约定称为单时钟同步纪律(single-clock synchronous discipline):

上图左边的电路中,带边沿触发的符号的方形表示寄存器,小云朵表示组合逻辑电路。

  • 寄存器输入到输出无组合通路,整个电路无组合环。换句话说,从系统输入和寄存器输出到寄存器输入的路径不会两次经过同一组合块。
  • 所有时钟器件共享同一周期时钟。使用多时钟域也可以,但跨域时序很难分析,同钟更简单。
  • 各数据信号何时变化大体不重要;关键是接到寄存器输入的信号稳定够久以满足建立时间,并保持够久以满足保持时间。

选时钟周期大于“寄存器输出 → 寄存器输入”每条路径的 tPDt_{\mathrm{PD}} 再加上建立时间,即可保证遵守动态纪律:

tCLK>tPD,path+tSETUPt_{\mathrm{CLK}} > t_{\mathrm{PD,path}} + t_{\mathrm{SETUP}}

额外好处:按这种方式选时钟周期后,上升沿瞬间电路中没有其它会引入噪声的逻辑跳变,因而更新各寄存器存储状态时不应有噪声问题。[3]

17. 单时钟系统中的时序(Timing in a Single-clock System)

Timing in a Single-clock System

分析某条路径:上游寄存器 → 组合逻辑 → 下游寄存器(逻辑输出记 STAR)。大系统有多条路径,须逐条分析以找出决定最小可用时钟周期的路径(CAD 工具常代劳)。

上升沿触发上游寄存器:QR1Q_{\mathrm{R1}} 在至少 tCD,REG1t_{\mathrm{CD,REG1}} 内保持旧值,并在 tPD,REG1t_{\mathrm{PD,REG1}} 前达到新稳定值,直至下一上升沿。组合逻辑的 tCDt_{\mathrm{CD}}/tPDt_{\mathrm{PD}} 再决定 STAR 最早失效与最晚稳定的时刻。

对下游 REG2:

  • t1t_1 = REG1 的 tCDt_{\mathrm{CD}} + 组合逻辑的 tCDt_{\mathrm{CD}}:上升沿后 STAR 仍有效多久;须 t1t_1 \ge REG2 的保持时间。
  • t2t_2 = REG1 的 tPDt_{\mathrm{PD}} + 组合逻辑的 tPDt_{\mathrm{PD}} + REG2 的建立时间:下一上升沿最早允许发生的时刻;须 t2tCLKt_2 \le t_{\mathrm{CLK}}(时钟周期)。过早则违反 REG2 动态纪律。

每条寄存器到寄存器路径都须满足这两个不等式。tCLKt_{\mathrm{CLK}} 不等式中,上游 tPDt_{\mathrm{PD}} 与下游建立时间从可用于“有用逻辑工作”的时间里抠掉,故设计者倾向选用这两项更小的寄存器。

若上下游之间没有组合逻辑(移位寄存器、数字延迟线等),第一不等式要求上游 tCDt_{\mathrm{CD}} \ge 下游保持时间;实践中污染延迟常小于保持时间,往往要插入哑逻辑(如两级反相器)制造足够 tCDt_{\mathrm{CD}}

还须考虑时钟偏斜(clock skew):因为时钟信号到达不同寄存器存在“时间差”(即时钟偏斜),为了确保数据不报错,下游寄存器不得不预留更宽裕的“稳定观察期”(即增加了等效的建立时间和保持时间)。

时钟周期 tCLKt_{\mathrm{CLK}} 刻画了系统性能。Intel 等按不同时钟频率卖处理器,例如 1.7 GHz2 GHz。这些芯片有何不同?其实没有:制造工艺波动使部分芯片的 tPDt_{\mathrm{PD}} 更好;逻辑 tPDt_{\mathrm{PD}} 更小,就可取更小 tCLKt_{\mathrm{CLK}}、更高频率。同一设计量产许多颗,测 tPDt_{\mathrm{PD}} 后把快的挑出来当更高性能档卖——这是芯片生意挣钱的秘诀。

18. 模型:离散时间(Model: Discrete Time)

Model Discrete Time

用 D 寄存器作时序系统的存储很合适:每个时钟上升沿加载下一状态,在余下时钟周期内作为当前状态出现在输出。组合逻辑用当前状态与输入计算下一状态与输出。一串上升沿与输入产生状态序列,进而产生输出序列。下一章引入有限状态机(finite state machine)抽象,便于设计时序系统。

19. 时序电路时序分析(Sequential Circuit Timing)

Sequential Circuit Timing

对图示时序系统套用上述分析(寄存器与组合逻辑时序参数如图):

  • 组合逻辑 tCDt_{\mathrm{CD}} 未给:要正确工作,寄存器与逻辑的 tCDt_{\mathrm{CD}} 之和须 \ge 寄存器保持时间 → 逻辑 tCDt_{\mathrm{CD}} 至少 1 ns
  • 最小 tCLKt_{\mathrm{CLK}}:须大于寄存器与逻辑的 tPDt_{\mathrm{PD}} 之和再加上建立时间 → 最小时钟周期 10 ns
  • 相对上升沿,Input 的建立时间 = 逻辑 tPDt_{\mathrm{PD}} + 寄存器建立时间 = 7 ns;保持时间 = 寄存器保持时间 − 逻辑 tCDt_{\mathrm{CD}} = 1 ns(换句话说,如果 Input 在时钟上升沿后保持至少 1 ns,那么 Next State 就能在时钟上升沿后保持至少 2 ns)。这样 Next State 才能分别满足寄存器的建立/保持。

20. 小结(Summary)

Summary

本讲完成时序逻辑入门:几乎所有数字系统都是时序系统,并遵守动态纪律施加的时序约束。下次看到 “1.7 GHz 处理器” 广告,就知道 “1.7” 从何而来。


  1. 译者注:模拟电子电路(analog electronics)主要指以下几个关键的模拟电路组件和机制:

    • 敏感放大器(Sense Amplifier):

      • 在读取电容存储的数据时,电容释放或吸收的电荷极少,只能导致位线(Bit line)产生非常微小的电压变化(远未达到标准数字电路的 0 或 1 电平)。
      • 需要专门设计的高灵敏度模拟放大器来检测这一微弱的连续电压差,并将其放大还原为标准的数字逻辑电平(逻辑 0 或逻辑 1)。
    • 微小电荷与连续电压的处理(Charge Sharing & Leakage Control):

      • 电容上的电荷存储和衰减(泄漏)、位线上的电荷共享(Charge sharing)都是连续变化的模拟物理过程,而非瞬间完成的离散数字状态。
      • 电路需要精准控制 NFET 晶体管开关的导通电阻、阈值电压以及微小的泄漏电流(Leakage current),这些器件特性的设计和优化都属于模拟电路设计的范畴。
    ↩︎
  2. 译者注:上文门控信号 GG(即后文的 CLK)在 CMOS 中接到钟控反相器传输门的栅极。以下给出两种基本单元与完整 D 锁存器电路,展示 CLK 接到 MOS 管的哪个端点。电路图由 Schemdraw 绘制(也可参考我的另一篇文章「Schemdraw 绘制 IC 原理图」)。

    钟控反相器(Clocked Inverter) 标准 CMOS 反相器各串一个时钟管,CLK/CLK̄ 接到时钟管的栅极。CLK=HIGH 时反相器正常工作;CLK=LOW 时输出高阻(floating)。有两种常见串联顺序:

    钟控反相器实现一 钟控反相器实现二
    钟控反相器实现一:时钟管在外侧 钟控反相器实现二:时钟管在中间
    Schemdraw 绘图代码:钟控反相器实现一
    05-seq-clk-inv-01.pyview raw
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    import schemdraw
    import schemdraw.elements as elm

    # 钟控反相器实现一:时钟管在外侧
    # 栈序(上→下):MP_clk(CLK̄) → MP_in(IN) → [OUT] → MN_in(IN) → MN_clk(CLK)

    with schemdraw.Drawing() as d:
    d.config(fontsize=12, color='black', bgcolor='white')

    MP_clk = d.add(elm.PFet().right().reverse())
    d.add(elm.Line().down().at(MP_clk.drain).length(0.4))
    MP_in = d.add(elm.PFet().right().reverse().anchor('source'))
    d.add(elm.Line().down().at(MP_in.drain).length(0.4))
    MN_in = d.add(elm.NFet().right().reverse().anchor('drain'))
    d.add(elm.Line().down().at(MN_in.source).length(0.4))
    MN_clk = d.add(elm.NFet().right().reverse().anchor('drain'))

    d.add(elm.Line().up().at(MP_clk.source).length(0.8))
    d.add(elm.Label().label('VDD'))
    d.add(elm.Ground().at(MN_clk.source))

    # OUT 节点
    d.add(elm.Dot().at(MP_in.drain))
    d.add(elm.Line().right().at(MP_in.drain).length(1).label('OUT', loc='end'))
    out_y = float(MP_in.drain[1]) # OUT 所在的 y 坐标

    # 时钟管栅极
    d.add(elm.Line().left().at(MP_clk.gate).length(1)
    .label(r'$\overline{CLK}$', loc='left'))
    d.add(elm.Line().left().at(MN_clk.gate).length(1)
    .label('CLK', loc='left'))

    # IN 栅极:竖线连接 MP_in.gate → MN_in.gate,
    # 在与 OUT 同高处(out_y)打圆点并引出 IN 标签,使 IN 与 OUT 同水平
    d.add(elm.Line().at(MP_in.gate).to(MN_in.gate))
    gate_x = float(MP_in.gate[0])
    d.add(elm.Dot().at((gate_x, out_y)))
    d.add(elm.Line().left().at((gate_x, out_y)).length(1.5)
    .label('IN', loc='left'))

    d.save('05-seq-clk-inv-01.svg')
    Schemdraw 绘图代码:钟控反相器实现二
    05-seq-clk-inv-02.pyview raw
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    import schemdraw
    import schemdraw.elements as elm

    # 钟控反相器实现二:时钟管在中间
    # 栈序(上→下):MP_in(IN) → MP_clk(CLK̄) → [OUT] → MN_clk(CLK) → MN_in(IN)

    with schemdraw.Drawing() as d:
    d.config(fontsize=12, color='black', bgcolor='white')

    MP_in = d.add(elm.PFet().right().reverse())
    d.add(elm.Line().down().at(MP_in.drain).length(0.4))
    MP_clk = d.add(elm.PFet().right().reverse().anchor('source'))
    d.add(elm.Line().down().at(MP_clk.drain).length(0.4))
    MN_clk = d.add(elm.NFet().right().reverse().anchor('drain'))
    d.add(elm.Line().down().at(MN_clk.source).length(0.4))
    MN_in = d.add(elm.NFet().right().reverse().anchor('drain'))

    d.add(elm.Line().up().at(MP_in.source).length(0.8))
    d.add(elm.Label().label('VDD'))
    d.add(elm.Ground().at(MN_in.source))

    # OUT 节点
    d.add(elm.Dot().at(MP_clk.drain))
    d.add(elm.Line().right().at(MP_clk.drain).length(1).label('OUT', loc='end'))
    out_y = float(MP_clk.drain[1]) # OUT 所在的 y 坐标,IN 也将对齐到此高度

    # CLK 管栅极(中层,较短连线居左)
    d.add(elm.Line().left().at(MP_clk.gate).length(1.0)
    .label(r'$\overline{CLK}$', loc='left'))
    d.add(elm.Line().left().at(MN_clk.gate).length(1.0)
    .label('CLK', loc='left'))

    # IN 管栅极:顶(MP_in.gate)和底(MN_in.gate)需绕过中层 CLK 管
    # 向左延伸至 in_far_x,用垂直线连接两栅,
    # 在与 OUT 同高处(out_y)打圆点并引出 IN 标签
    gate_x = float(MP_in.gate[0])
    in_far_x = gate_x - 2.2

    d.add(elm.Line().left().at(MP_in.gate)
    .to((in_far_x, float(MP_in.gate[1]))))
    d.add(elm.Line().down().at((in_far_x, float(MP_in.gate[1])))
    .to((in_far_x, float(MN_in.gate[1]))))
    d.add(elm.Line().right().at((in_far_x, float(MN_in.gate[1])))
    .to(MN_in.gate))

    # IN 引出点与 OUT 同高
    d.add(elm.Dot().at((in_far_x, out_y)))
    d.add(elm.Line().left().at((in_far_x, out_y)).length(0.5)
    .label('IN', loc='left'))

    d.save('05-seq-clk-inv-02.svg')

    传输门(Transmission Gate) NFET(栅 = CLK)与 PFET(栅 = CLK\overline{\text{CLK}})并联。CLK=HIGH 时双向导通(无阈值损失);CLK=LOW 时断开。

    传输门

    Schemdraw 绘图代码:传输门
    05-seq-tg-01.pyview raw
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    import schemdraw
    import schemdraw.elements as elm

    with schemdraw.Drawing() as d:
    d.config(fontsize=12, color='black', bgcolor='white')

    # PFET(左侧):source=上(IN),drain=下(OUT),gate=左(CLK̄)
    MP = d.add(elm.PFet().right().reverse())

    # NFET(右侧):drain=上(IN),source=下(OUT),gate=右(CLK)
    # 以 drain 为入笔锚点,使 drain 与 MP.source 等高
    MN = d.add(
    elm.NFet().right()
    .at((MP.source[0] + 2.8, MP.source[1]))
    .anchor('drain')
    )

    # IN 横线(顶部):MP.source → MN.drain
    d.add(elm.Line().at(MP.source).to(MN.drain))
    in_x = (MP.source[0] + MN.drain[0]) / 2
    in_y = float(MP.source[1])
    d.add(elm.Dot().at((in_x, in_y)))
    d.add(elm.Line().up().at((in_x, in_y)).length(0.8).label('IN', loc='top'))

    # OUT 横线(底部):MP.drain → MN.source
    d.add(elm.Line().at(MP.drain).to(MN.source))
    out_x = (MP.drain[0] + MN.source[0]) / 2
    out_y = float(MP.drain[1])
    d.add(elm.Dot().at((out_x, out_y)))
    d.add(elm.Line().down().at((out_x, out_y)).length(0.8).label('OUT', loc='bot'))

    # 栅极标签
    d.add(elm.Line().left().at(MP.gate).length(1.2)
    .label(r'$\overline{CLK}$', loc='left'))
    d.add(elm.Line().right().at(MN.gate).length(1.2)
    .label('CLK', loc='right'))

    d.save('05-seq-tg-01.svg')

    D 锁存器(双钟控反相器) CI1(CLK=HIGH 导通)将 D 反相至 Q\overline{Q};CI2(CLK=LOW 导通)将 Q\overline{Q} 反相反馈回 D 节点,构成保持环。

    D 锁存器

    Schemdraw 绘图代码:D 锁存器
    05-seq-dlatch-01.pyview raw
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    import schemdraw
    import schemdraw.elements as elm

    # D 锁存器:两个背靠背钟控反相器(2×4 管)
    # CI1(CLK=HIGH 时导通):D → Q̄
    # CI2(CLK=LOW 时导通):Q̄ → D(正反馈保持状态)

    with schemdraw.Drawing() as d:
    d.config(fontsize=12, color='black', bgcolor='white')

    # ── CI1 ──────────────────────────────────────────────
    MP_clk1 = d.add(elm.PFet().right().reverse())
    d.add(elm.Line().down().at(MP_clk1.drain).length(0.4))
    MP_in1 = d.add(elm.PFet().right().reverse().anchor('source'))
    d.add(elm.Line().down().at(MP_in1.drain).length(0.4))
    MN_in1 = d.add(elm.NFet().right().reverse().anchor('drain'))
    d.add(elm.Line().down().at(MN_in1.source).length(0.4))
    MN_clk1 = d.add(elm.NFet().right().reverse().anchor('drain'))

    d.add(elm.Line().up().at(MP_clk1.source).length(0.8).label('VDD', loc='top'))
    d.add(elm.Ground().at(MN_clk1.source))

    # CI1 输入门(D 节点)
    d.add(elm.Line().at(MP_in1.gate).to(MN_in1.gate))
    d_mid = 0.5 * (MP_in1.gate + MN_in1.gate)
    d.add(elm.Dot().at(d_mid))
    d.add(elm.Line().left().at(d_mid).length(1.2).label('D', loc='left'))

    # CI1 时钟管标签
    d.add(elm.Line().left().at(MP_clk1.gate).length(1.2)
    .label(r'$\overline{CLK}$', loc='left'))
    d.add(elm.Line().left().at(MN_clk1.gate).length(1.2)
    .label('CLK', loc='left'))

    # CI1 输出节点(Q̄)
    d.add(elm.Dot().at(MP_in1.drain))

    # ── CI2 ──────────────────────────────────────────────
    x2 = float(MP_clk1.source[0]) + 4.5
    y2 = float(MP_clk1.source[1])

    MP_clk2 = d.add(elm.PFet().right().reverse().at((x2, y2)))
    d.add(elm.Line().down().at(MP_clk2.drain).length(0.4))
    MP_in2 = d.add(elm.PFet().right().reverse().anchor('source'))
    d.add(elm.Line().down().at(MP_in2.drain).length(0.4))
    MN_in2 = d.add(elm.NFet().right().reverse().anchor('drain'))
    d.add(elm.Line().down().at(MN_in2.source).length(0.4))
    MN_clk2 = d.add(elm.NFet().right().reverse().anchor('drain'))

    d.add(elm.Line().up().at(MP_clk2.source).length(0.8).label('VDD', loc='top'))
    d.add(elm.Ground().at(MN_clk2.source))

    # CI2 输入门(Q̄ 节点)
    d.add(elm.Line().at(MP_in2.gate).to(MN_in2.gate))
    qbar_gate = 0.5 * (MP_in2.gate + MN_in2.gate)
    d.add(elm.Dot().at(qbar_gate))

    # CI2 时钟管标签(CLK/CLK̄ 互换)
    d.add(elm.Line().left().at(MP_clk2.gate).length(1.2)
    .label('CLK', loc='left'))
    d.add(elm.Line().left().at(MN_clk2.gate).length(1.2)
    .label(r'$\overline{CLK}$', loc='left'))

    # CI2 输出节点(反馈至 D)
    d.add(elm.Dot().at(MP_in2.drain))

    # ── CI1 输出 → CI2 输入(Q̄ 连线)──────────────────
    ci1_out = MP_in1.drain
    qbar_gate_x = float(qbar_gate[0])
    qbar_gate_y = float(qbar_gate[1])
    ci1_out_y = float(ci1_out[1])

    # 水平段
    d.add(elm.Line().right().at(ci1_out)
    .to((qbar_gate_x, ci1_out_y)))
    # 垂直修正段(如不对齐)
    if abs(ci1_out_y - qbar_gate_y) > 0.05:
    d.add(elm.Line().at((qbar_gate_x, ci1_out_y)).to(qbar_gate))

    # Q̄ 标签
    qbar_label_x = (float(ci1_out[0]) + qbar_gate_x) / 2
    d.add(elm.Label().at((qbar_label_x, ci1_out_y))
    .label(r'$\overline{Q}$', loc='top'))

    # ── CI2 输出 → D 节点(正反馈连线)────────────────
    ci2_out = MP_in2.drain
    fb_x = float(ci2_out[0]) + 1.5
    fb_y = float(MN_clk2.source[1]) - 1.5

    d.add(elm.Line().right().at(ci2_out).length(1.5))
    d.add(elm.Line().down().at((fb_x, float(ci2_out[1]))).to((fb_x, fb_y)))
    d.add(elm.Line().left().at((fb_x, fb_y)).to((float(d_mid[0]), fb_y)))
    d.add(elm.Line().up().at((float(d_mid[0]), fb_y)).to(d_mid))

    d.save('05-seq-dlatch-01.svg')
    ↩︎
  3. 译者注:这句话点出单时钟同步电路(single-clock synchronous system)的一个精妙优势——通过合理控制时钟周期,把电路噪声与状态更新彻底“解耦”(错开)。可从三层理解:

    1. 逻辑翻转是电路噪声的主要来源。 CMOS 中信号 0↔1(逻辑翻转/过渡)会产生瞬时大电流(充放电电流及导通短路电流)。电流突变导致电源电压波动(电源噪声 / ground bounce),也会经寄生电容干扰邻近信号线(串扰 crosstalk)。

    2. “按这种方式选择时钟周期”做了什么。 指将 TCLKT_{\mathrm{CLK}} 设得足够长:

      TCLKtPD,reg+tPD,comb+tsetupT_{\mathrm{CLK}} \ge t_{\mathrm{PD,reg}} + t_{\mathrm{PD,comb}} + t_{\mathrm{setup}}

      从而保证:上一上升沿触发后,组合逻辑里所有“中间过渡与信号震荡/翻转”(noise-inducing logic transitions)都有足够时间完全停止并稳定下来。

    3. 为何上升沿到来时没有噪声问题。 下一时钟上升沿触发寄存器锁存/更新新状态的那一瞬,组合逻辑已“静止”很久,没有线缆在发生信号翻转。电路内部没有其它翻转带来的电源噪声或串扰,寄存器可在安静环境下精准采样并写入新状态,稳定性与可靠性更高。

    形象比喻:组合逻辑计算像拍集体照前大家调整姿势、换位置(杂乱动作与噪音);足够长的时钟周期是给所有人留足时间站好不动。上升沿(快门按下)到来时所有人都已静止,拍出的照片(采样写入的数据)不会有模糊或重影(噪声干扰)。 ↩︎