JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

触发器、寄存器与时钟

你已经教会一根导线如何运算,现在该教它记忆了。到目前为止,你搭建的每一个电路都是组合逻辑——输出瞬间跟随输入,没有过去,没有未来,只是一道不停的回声。真实的芯片需要状态:一个知道自己数到哪里的计数器,一个保存着你最后写入值的寄存器。这份状态由两个协同工作的新概念带来——捕获一个比特的触发器,以及那道稳定的心跳、告诉每个触发器何时去看的时钟。本指南为你的硬件加入记忆与节奏,再引入那场安静的时序握手——建立时间与保持时间——它让整个系统值得信赖。

为硬件加入记忆:状态

问自己一个朴素的问题:电路是怎么记住任何东西的?纯组合逻辑做不到——接上几个逻辑门,输出不过是*此刻*输入的一个即时函数。切断输入,答案就消失了,就像一台没有记忆键的计算器。要计数、要保住一个结果、要等待下一条指令,硬件需要一种办法,在成因消失之后仍然守住一个值

那个被存住的值有一个名字:状态。带状态的电路就是时序逻辑——它的输出不仅取决于此刻的输入,还取决于之前发生过什么。想想电灯开关和门铃的区别:门铃只在你按着的时候响(组合逻辑),而开关会*停留*在你拨到的位置(状态)。本指南的全部工作,就是用逻辑门搭出那个开关,并给它一个有纪律的时刻去改变。

重新认识触发器

状态的最小单位是一个比特,而守住它的器件就是触发器——具体来说是D触发器,数字设计里的主力。把它想成一台带快门的相机。镜头一直看着场景(也就是D,即*数据*输入),但照片只在快门咔嚓一声的那一瞬间才更新。D触发器正是如此:D上是什么值,都会在那个锐利的一刻被捕获,然后稳稳地保持在输出 Q 上,直到下一次咔嚓。

那声“咔嚓”就是时钟边沿——时钟线从0跳到1的那一刻。在两次边沿之间,D尽可以随便抖动,触发器一概不理。只有在上升沿,Q才会对 D 拍下一张全新的快照。这就是从组合逻辑跃迁到时序逻辑的那一步:输出从此按时刻表改变,而不再连续不断地变。

module dff(input clk, input d, output reg q);
  always @(posedge clk)   // at each rising clock edge...
    q <= d;               // ...Q snapshots D, then holds
endmodule
一个 D 触发器:一个比特的记忆。在每个时钟上升沿,q取走 d 的值,并一直保持到下一个边沿。

寄存器:守住一个值

一个触发器只守一个比特——拿来当标志位还行,存一个数字就没用了。把N个触发器并排叠在一起,全都共享同一个[[clock-signal|时钟]],你就得到了一个寄存器:一排快门同时咔嚓,在同一瞬间捕获一个 N 位的值。一个8位寄存器,无非就是八个 D 触发器、把它们的时钟引脚接到一起。边沿一到,八个一起同时给各自的比特拍下快照——整个字节作为一体落定。

在 Verilog 里,你很少真去手工连八个触发器。你只要声明一个向量,让综合替你搭出这一排。下面这段代码描述了一个8位寄存器:和单个比特一样的 `posedge` 捕获,只是更宽。有一条提醒值得你带过新手的第一天:`reg` 只是 Verilog 给“过程性赋值变量”起的名字,并不是记忆的保证——只有当你像这样在时钟边沿上给一个 `reg` 赋值时,它才会真正变成一个触发器。把同一个 `reg` 放在组合逻辑块里赋值,综合出来的就是纯粹的逻辑门,根本没有任何存储。

module reg8(input clk, input [7:0] d, output reg [7:0] q);
  always @(posedge clk)
    q <= d;        // all 8 bits captured together on the edge
endmodule
一个8位寄存器:八个 D 触发器共享一个时钟。整个字节在每个上升沿被一起捕获。

always @(posedge clk)

这一行就是同步设计的心跳,所以它值得单开一节。`always @(posedge clk)` 告诉工具:*这个块里的硬件只在 `clk` 的上升沿更新。*你在这里写下的一切都会变成时序逻辑——它会得到触发器。`posedge` 这个关键字是相机的快门;`clk` 就是那根按着稳定节拍去按快门的手指。

在时钟驱动的块里,要用非阻塞赋值 `<=`,而不是 `=`。直觉是这样的:块里每一个 `<=` 都用值去读自己的右边,然后所有左边在边沿处一起更新——这恰恰是真实触发器的行为,一齐拍下快照。在这里用 `=`(阻塞赋值),你描述的就成了一份按部就班的菜谱,而那正是 HDL 想要帮你戒掉的软件思维。

always @(posedge clk) begin
  q1 <= d;     // all right-hand sides are read FIRST (old values),
  q2 <= q1;    // then all left-hand sides update together at the edge
end
非阻塞赋值 <= 模拟的是真实触发器的行为:q2 取到的是旧的 q1,而不是新的。这搭出来的是一个两级移位,而不是塌成一级。

建立时间与保持时间:那场时序握手

这是你第一次真正尝到时序的滋味,而它远没有传说中那么吓人。触发器没法可靠地捕获一个还在变动的值——快门需要场景稳住片刻。具体来说,D输入必须在时钟边沿前后的一个小窗口里保持稳定:边沿之前一点点,边沿之后一点点。

这个安静窗口的两半各有名字。**建立时间**是 D 在边沿之前必须保持稳定的时长;**保持时间**是它在边沿之后必须继续稳定的时长。违反其中任何一个——让 D 在这个窗口里变动——捕获就不再有保证:触发器可能只是锁住了一个错误(但干净)的值,也可能落入一种模糊、半解析的状态,叫做*亚稳态*,此时 Q 徘徊在0和1之间,要经过一段无法预测的延迟才会安定下来。把它想成一张在运动中按下快门拍出的照片:拖影、不可靠、没法用。

这些你通常不用手算——静态时序分析替你检查设计里的每一个触发器,并报出裕量(剩下的那点时序余地)。但直觉才是整座地基:相机咔嚓的那一刻,数据必须静止。把时钟拨得太快,信号就来不及在限定时间内安定下来——而这恰恰是为什么每一块芯片都有一个最高时钟频率。

同步设计的纪律

所有这些零件,最终汇聚成一个专业习惯:同步设计。这条规则说起来很简单,却值得近乎虔诚地遵守——只用一个时钟,让设计里的每一个触发器都在它的同一个边沿上捕获。一道共享的心跳,整块芯片便齐步向前,一拍走一步。

为什么要这么严?因为它让时序可被分析。只用一个时钟,“每个信号都按时到了吗?”这个问题,在每个时钟周期里都有一个干净的答案,而时序分析就能机械地把整块芯片验证一遍。一旦混进了乱七八糟的时钟,或是任意对信号边沿做出反应的逻辑(这叫*异步*设计),就几乎没法验证了,并且会以那种只在某些芯片上、某些日子里、某些温度下才冒出来的臭虫而臭名昭著。

于是,整块同步芯片的心智模型是这样的:一团团组合逻辑在计算下一个值,被一排排寄存器隔开,这些寄存器在时钟边沿一起拍下快照。计算、捕获、再来——在周期里计算,在边沿处捕获。正是这道心跳,把一堆逻辑门变成了一台穿行于时间之中的机器。