从沙子到车辙(3.2):时序逻辑——有记忆的计算
3.2 时序逻辑有记忆的计算本文内容摘自本人的开源书《从沙子到车辙 - 一个工程师的理解》 在线阅读/下载from-sand-to-rutsgitclone https://github.com/Lularible/from-sand-to-ruts⭐ 如果对您有帮助欢迎 Star 支持也欢迎通过 GitHub Issues 交流讨论。在时钟边沿把 bit 存下来你盯着示波器的屏幕。通道 1 上是一个漂亮的矩形波——100MHz周期 10ns。通道 2 上是数据信号——一堆 0 和 1 在疯狂翻转。你对着信号说了句话“在下一个时钟上升沿把这个 bit 存下来。”这一刻你在要求电路拥有记忆。上一节我们讲了组合逻辑——输出永远等于当前输入的函数。但这句话意味着输出不再只是当前输入的函数了。输出取决于时钟边沿到来那一刻输入是什么——并且这个值会被保持直到下一个边沿。这需要一个全新的元件。D 触发器1 bit 的记忆记忆的最基本单元叫做D 触发器D Flip-FlopFF。它只有一个数据输入D、一个数据输出Q、一个时钟输入CLK。行为简单到可以用一句话描述在每个 CLK 的上升沿Q ← D 其余时间Q 保持不变CLK: _|¯|_|¯|_|¯|_|¯|_|¯|_ D : ___ ¯¯¯¯¯¯ _______ ¯¯¯¯ Q : _________ ¯¯¯¯¯¯ ______ ↑ ↑ 第一个上升沿 第二个上升沿一个 D 触发器存储了1 bit的信息。它是怎么做到保持的内部结构其实很优雅——用两个锁存器latch级联每个锁存器用透明/锁存两种模式交替工作。CLK0 时主锁存器透明、从锁存器锁存CLK1 时反过来。数据在 CLK 上升沿那一个瞬间从 D 被拍进 Q然后被锁死。把 N 个 D 触发器放在一起、用同一个 CLK——这就是一个N 位寄存器。你的 ARM Cortex-M4 有 16 个通用寄存器R0-R15每个 32 位。本质上是 16 × 32 512 个 D 触发器被同一个 CLK 驱动。加上 PC、SP、LR、PSR——总共几百个 FF构成了 CPU 的寄存器文件register file。用 C 模拟 D 触发器让我们在软件里模拟一个 D 触发器包含建立时间和保持时间的检查#includestdint.h#includestdio.htypedefstruct{intq;// 当前输出doublet_setup;// 建立时间 (ps)doublet_hold;// 保持时间 (ps)doublet_cq;// Clock-to-Q 延迟 (ps)intmetastable;// 亚稳态标志}D_FlipFlop;voiddff_clock(D_FlipFlop*ff,intd,doubled_change_time,doubleclk_edge_time){// 检查建立时间数据必须在时钟边沿之前 t_setup 稳定if(clk_edge_time-d_change_timeff-t_setup){ff-metastable1;ff-q-1;// 不确定值return;}// 检查保持时间数据在时钟边沿之后 t_hold 内不能变//在完整模拟中需要 scheduler 支持这里简化ff-metastable0;ff-qd;// 正常采样}voiddemo_dff(){D_FlipFlop ff{0,50.0,20.0,100.0,0};// 正常采样数据在边沿前 80ps 已稳定50ps 建立时间dff_clock(ff,1,100.0,180.0);printf(Q %d (正常)\n,ff.q);// Q 1// 建立时间违例数据在边沿前 30ps 才变50ps 建立时间dff_clock(ff,0,250.0,280.0);printf(Q %d, metastable %d (亚稳态!)\n,ff.q,ff.metastable);// Q 不确定}这个模拟抓住了关键D 触发器的行为不是即时的。数据不能在时钟边沿附近变化——它需要一段安静时间。违反这个要求触发器就进入亚稳态。同步时序电路组合逻辑 寄存器当你把组合逻辑和寄存器组合起来就得到了同步时序电路-------- -------- -------- | 寄存器 |────| 组合 |────| 寄存器 |──── ... | (FFs) | | 逻辑 | | (FFs) | -------- -------- -------- ↑ ↑ CLK CLK每一个时钟周期前级寄存器输出稳定数据。组合逻辑用这一拍的时间来传播和计算。结果在下一个上升沿被拍进后级寄存器。这就是所有同步数字电路的基本架构。从最简单的计数器到最复杂的多核 SoC都是这个架构的递归嵌套——寄存器、云、寄存器、云、寄存器……一层套一层。一个 4 位计数器时序逻辑的最简实例#includestdint.htypedefstruct{uint8_tvalue;// 4-bit 计数值uint8_tenable;// 使能uint8_treset;// 同步复位}Counter4Bit;voidcounter_clock(Counter4Bit*ctr){// 同步复位在时钟上升沿采样if(ctr-reset){ctr-value0;return;}if(ctr-enable){// 用上一节定义的全加器逻辑加 1inta[4]{ctr-value1,(ctr-value1)1,(ctr-value2)1,(ctr-value3)1};intone[4]{1,0,0,0};intsum[4],c_out;ripple_carry_adder(a,one,0,sum,c_out);// 4-bit 环绕忽略 c_outctr-valuesum[0]|(sum[1]1)|(sum[2]2)|(sum[3]3);}// 无使能保持原值}这个 4 位计数器——enabled 时在 CLK 上升沿加 1reset 时归零。它只有 4 个 D 触发器和一个 4 位加法器。但把它放大到 64 位加上一个起始值再用一个捕获信号——你就得到了 PTP 硬件时钟PHC的核心。PTP 从时钟内部就是一个巨大的计数器从 0 开始昼夜不停地数——数的是本地振荡器的周期。PTP 报文到达时硬件把那个瞬间的计数值锁存下来这就是硬件时间戳。全部由一串 D 触发器和加法器完成。移位寄存器把 N 个 D 触发器串联——前一个的 Q 接到后一个的 D——就得到一个移位寄存器。每个时钟周期数据向右移动一位。CLK: _|¯|_|¯|_|¯|_|¯|_|¯|_|¯|_ D_in: ___ ¯¯¯¯ _____ ______________ Q0: ______ ¯¯¯¯ _____ __________ Q1: _________ ¯¯¯¯ _____ _______ Q2: ____________ ¯¯¯¯ _____ ____移位寄存器是串并转换SPI 的数据接收和并串转换SPI 的数据发送的物理基础。UART 的发送端也是一个移位寄存器——并行数据加载进去每个波特率时钟移出一位到 TX 线。三个关键时序参数但拍这个动作不是魔法。D 触发器对时序有严格的要求。Setup time建立时间数据必须在时钟边沿之前稳定下来的最短时间。如果数据在建立时间窗口内还在变FF 抓到的可能是 0 也可能是 1——甚至是一个中间电平。这叫亚稳态metastability。Hold time保持时间数据必须在时钟边沿之后继续保持稳定一段时间。Clock-to-Q delay时钟边沿之后Q 端新数据稳定输出所花的时间。想象地铁车门。你必须在门开始关闭之前踏进车厢——这就是Setup Time。门关到一半的时候你不能又伸一只脚进去——这就是Hold Time。如果违反了Setup——你被门夹住了亚稳态。如果违反了Hold——你的脚被夹断数据丢失。D触发器的时钟边沿就是那扇正在关闭的门。整个数字电路的最大频率由这个公式决定f_max 1 / (T_cq T_comb T_setup T_skew)其中 T_comb 是两级寄存器之间的组合逻辑最大路径延迟T_skew 是时钟偏差——同一个 CLK 到达不同 FF 的时间差异。在典型工艺下两级寄存器之间放十几级逻辑门每级约几十皮秒。T_cq、T_setup、T_skew 都在几十皮秒到上百皮秒级别。一个 15 级逻辑深度的路径总延迟约几百皮秒f_max 在 1-2 GHz 范围内。但如果切换到最差工艺角慢 NMOS、慢 PMOS、高温、低压的条件每级门延迟可能增加 50% 以上T_cq 也会显著增加。同一个设计在不同工艺角下最高频率可以差 50%。这就是为什么芯片要标工作频率范围而不是固定频率——它不是不想稳定是物理做不到稳定。这也是为什么 IC 内部能跑 GHz、而分立芯片方案只有 MHz 的原因。IC 内部的走线极短、时钟 skew 可控、门延迟极小。PCB 上走线长、电容大、电磁干扰多——T_comb 和 T_skew 都大得多。亚稳态当触发器犹豫不决上面提到了亚稳态。这是一个值得专门讨论的概念——因为它是所有跨时钟域CDC设计的核心问题。D 触发器的内部有一个正反馈环——两个反相器首尾相连。正常工作状态下这个反馈环被强制拉到 0 或 1。但如果数据在建立时间窗口内变化输入锁存器捕获到的可能是一个介于 V_IL 和 V_IH 之间的电压——既不是干净的 0也不是干净的 1。这时反馈环内的两个反相器会进入线性区——它们像一个放大器试图把输入放大到满摆幅。但如果输入电压恰好是反馈环的亚稳态点约 VDD/2两个反相器的增益恰好让输出等于输入——整个环停留在中间电压上。它不会永远停在中间——热噪声最终会打破平衡让它跌落到 0 或 1。但这个过程需要的时间是不确定的——可能是一个时钟周期也可能是几百个时钟周期。在这段时间内后续的逻辑电路可能看到一个 0可能看到一个 1可能看到振荡——这意味着计算结果是不可重复的。这就是为什么跨时钟域时必须放同步器——两级或三级 D 触发器串联给第一个触发器足够的平均故障间隔时间MTBF从亚稳态恢复。两级同步器的 MTBF 通常在几十年以上对于 200MHz 时钟域但对于 ASIL-D 系统通常还是需要三级同步器或异步 FIFO。亚稳态不是设计错误是物理规律。你无法消除它只能把它发生的概率降到可接受的范围。PTP 硬件时间戳触发器的终极应用现在回过头来看 PTP。PTP 从时钟需要记录报文到达 MAC 层的精确时刻精度要求在纳秒级。怎么做到的现代支持 PTP 的以太网 MAC比如 TI DP83640的做法在 MII/RMII 接口上监测 RX_DV接收数据有效信号的跳变。RX_DV 变为高电平的瞬间硬件电路产生一个捕获信号。这个捕获信号触发一组寄存器即 D 触发器阵列锁存当前 PTP 硬件时钟PHC的计数值。整个过程在物理层完成延迟只有几个纳秒抖动在亚纳秒级别。没有中断响应时间没有软件处理延时没有操作系统的调度不确定性——因为全部是触发器和组合逻辑在干活。PTP 的纳秒精度怎么来的回答是它把记录时间这件事完全交给了一条以固定频率运转的计数器 一个边沿触发锁存的 D 触发器组。这是纯硬件没有任何软件的不确定性。而在 PTP 的 PI 伺服控制器中频率校正值写入 PHC 的频率调整寄存器——本质上是一个加法器每个时钟周期把频率校正值累加到一个相位累加器中溢出时产生一个时钟 tick。这也是纯时序逻辑。整个 PTP 协议的精髓不在软件——在硬件。在那些昼夜不停翻转着的触发器上。软件时间戳做不到这个精度因为软件只有轮询或中断——即使在 RTOS 上响应延迟也有微秒级。差了三个数量级。你在数电课上学过的 D 触发器——这个看起来最基础的单元——是 PTP 硬件时间戳的物理基础。从死物到活物组合逻辑是瞬间的真理——输出永远是当前输入的函数。时序逻辑是历史的记忆——输出不只是当前输入的函数还带着过去的痕迹。这一记忆能力把电路从死物变成了活物。你在设计的任何系统本质上都是在计算和记忆之间找平衡你在ECU里写CAN诊断状态机的时候每一次状态转移——从默认会话到扩展会话——都是一组D触发器在时钟边沿更新自己的值。PTP的PI伺服控制器累计历史偏移用于频率校正——那个积分项的每个增量都锁存在一个寄存器里等待下一个Sync周期被读出来。记忆——就是电路学会等。你的 ECU 不是在计算——它是在用记忆辅助计算用计算保护记忆。本篇小结今天我们做了一件事理解了时序逻辑——D触发器给了电路记忆让它不再只是当前输入的函数。关键结论D触发器是1 bit记忆在每个时钟上升沿Q←D其余时间保持不变——N个串联就是寄存器寄存器组合逻辑就是所有同步数字电路的基本架构。三个时序参数决定芯片能跑多快T_cq T_comb T_setup T_skew 共同限制最大频率工艺角变化能让f_max差50%。亚稳态不是设计错误是物理规律你无法消除它只能把概率降到可接受范围——跨时钟域必须放同步器。下一节数据通路与控制器的双人舞——把加法器、寄存器、ALU编排成能执行程序的处理器。【下集预告】你现在手里有加法器、多路器、寄存器文件、ALU、程序计数器——都是前两节造出来的积木块。它们摊在桌面上每个都能用。但从桌面上你得不到一个能执行程序的处理器。你需要一个指挥。一个能把取指→译码→执行→写回这四个动作编排成一支舞的东西。下一节数据通路与控制器的双人舞。