现在位置: 首页 > 计算机组成原理 > 正文

触发器与寄存器 -- 计算机如何「记住」数据

从组合逻辑到时序逻辑,理解 D 触发器和寄存器的工作原理


生活类比:相机快门

想象你手中有一台相机。在按下快门之前,外面的世界千变万化——行人走动、树叶摇晃、光线明暗交替。

但当你按下快门的那个瞬间(时钟上升沿),当前画面被「锁存」到照片中。

之后无论外面的世界如何变化,照片里的内容永远定格在了那个瞬间。这就是 D 触发器的核心行为——在时钟信号的上升沿,捕捉并保存数据。


组合逻辑 vs 时序逻辑

此前几讲我们学习的与门、或门、非门等电路,都属于组合逻辑。它们的输出仅由当前的输入决定——输入变了,输出立刻跟着变(忽略传播延迟)。

但计算机要能「记住」数据,就需要一种能在输入消失后依然保持输出的电路。这就是时序逻辑

核心区别

组合逻辑像一盏灯——开关按下就亮,松手就灭,输出完全依赖当前输入。

时序逻辑像一个保险箱——你把东西放进去锁好(时钟上升沿到达),之后即使你走开(输入消失),里面的东西依然在。

对比维度 组合逻辑 时序逻辑
输出依赖 仅取决于当前输入 取决于当前输入和之前的状态
记忆能力 无(无状态) 有(能记住历史)
典型电路 与门、或门、非门、加法器 D 触发器、寄存器、计数器
时钟依赖 不依赖时钟 依赖时钟信号同步状态更新
反馈回路 无反馈(无环) 有反馈回路来保持状态

D 触发器的结构与行为

D 触发器(D Flip-Flop)是最基本的时序逻辑单元。它有两个输入和一个输出:

D(Data):数据输入端,表示要存储的值(0 或 1)。

CLK(Clock):时钟输入端,控制何时锁存数据。

Q:数据输出端,输出当前存储的值。

关键行为:D 触发器只在时钟信号的上升沿(从 0 跳变到 1 的瞬间)将 D 的值锁存到 Q。其他时间,无论 D 怎么变,Q 都保持不变。

这就像相机只在按下快门的瞬间记录画面,其他时间外面的画面怎么变都影响不到照片。

为什么需要时钟?

如果没有时钟信号,D 触发器的输出会随着输入不断变化,变成又一个组合逻辑电路。「记忆」就无从谈起。

时钟信号提供了一个同步节拍,让所有触发器在统一的时间点更新状态。整个计算机系统就是在这个节拍下协调工作的。


寄存器:多个触发器的并联

一个 D 触发器只能存储 1 位(bit)数据。要存储一个字节(8 位)或一个字(32 位),就需要多个触发器并联在一起。

把 N 个 D 触发器并联,让它们共享同一个时钟信号,就构成了一个 N 位寄存器(Register)

当时钟上升沿到来时,所有触发器同时锁存各自 D 端的值,这样就一次性保存了完整的 N 位数据。

多级寄存器与流水线

在 CPU 内部,寄存器的输出常常连接到下一级组合逻辑,再连接到下一级寄存器——形成寄存器-组合逻辑-寄存器的流水线结构。

每个时钟周期,数据从一级寄存器经组合逻辑计算后,在下一个上升沿存入下一级寄存器。这就是现代 CPU 流水线的基础。


代码演示:Python 模拟 D 触发器和寄存器

下面用 Python 代码模拟 D 触发器和 4 位寄存器的行为。重点关注上升沿检测的逻辑:当检测到 clk 从 0 跳变到 1 时,才捕获数据。

实例

class DFlipFlop:
    """
    D 触发器:在时钟上升沿锁存输入数据

    行为规则:
    - 当时钟从 0 跳变到 1(上升沿)时,将 D 输入锁存到 Q 输出
    - 其他时间(clk=0、clk=1 但非跳变时),Q 保持原值不变
    """

    def __init__(self, name="DFF"):
        self.q = 0           # 输出 Q,初始为 0
        self.prev_clk = 0    # 上一个时刻的时钟值,用于检测上升沿
        self.name = name

    def update(self, d, clk):
        """
        更新触发器状态

        参数:
            d:   数据输入 (0 或 1)
            clk: 时钟信号 (0 或 1)

        返回:
            当前的 Q 输出值

        上升沿检测:prev_clk == 0 且 clk == 1
        只有满足此条件时才将 d 的值锁存到 q
        """

        # === 上升沿检测 ===
        if self.prev_clk == 0 and clk == 1:
            self.q = d  # 在上升沿将 D 的值锁存到 Q
        self.prev_clk = clk  # 记录当前时钟状态,供下一周期使用
        return self.q

    def reset(self):
        """重置触发器状态"""
        self.q = 0
        self.prev_clk = 0


class Register4Bit:
    """
    4 位寄存器:4 个 D 触发器并联,共享同一时钟信号

    结构示意:
        D3 ──▶ [DFF3] ──▶ Q3 (高位)
        D2 ──▶ [DFF2] ──▶ Q2
        D1 ──▶ [DFF1] ──▶ Q1
        D0 ──▶ [DFF0] ──▶ Q0 (低位)
               ↑↑↑↑ 共享同一个 CLK

    当时钟上升沿到达时,4 个触发器同时锁存各自的 D 输入
    """

    def __init__(self):
        # 创建 4 个触发器,bit[3] 为最高位,bit[0] 为最低位
        self.flip_flops = [
            DFlipFlop("bit3"),
            DFlipFlop("bit2"),
            DFlipFlop("bit1"),
            DFlipFlop("bit0"),
        ]

    def update(self, data, clk):
        """
        更新寄存器

        参数:
            data: 要存储的数据 (0-15 的整数,超出范围会被截断)
            clk:  时钟信号 (0 或 1)

        返回:
            寄存器中当前存储的值 (0-15)
        """

        data = data & 0b1111  # 确保只有 4 位有效
        # 逐位送入对应的触发器,高位在前
        for i in range(4):
            # 从高到低提取每一位:bit3, bit2, bit1, bit0
            bit = (data >> (3 - i)) & 1
            self.flip_flops[i].update(bit, clk)
        return self.read()

    def read(self):
        """读取寄存器中当前存储的值(不改变状态)"""
        value = 0
        for ff in self.flip_flops:
            value = (value << 1) | ff.q
        return value

    def reset(self):
        """重置所有触发器"""
        for ff in self.flip_flops:
            ff.reset()


# ============================================================
# 模拟演示:多个时钟周期下寄存器的行为
# ============================================================
if __name__ == "__main__":
    reg = Register4Bit()

    # 测试数据序列:模拟在不同时钟周期写入不同数据
    test_data = [5, 10, 15, 0, 7, 13, 3, 8, 1, 14]
    # 5=0101, 10=1010, 15=1111, 0=0000, 7=0111
    # 13=1101, 3=0011, 8=1000, 1=0001, 14=1110

    print("=" * 55)
    print("  runoob - 4 位寄存器模拟演示")
    print("  观察:数据只在时钟上升沿被锁存进寄存器")
    print("=" * 55)
    print(f"{'周期':<6} {'时钟':<10} {'输入数据 D':<12} {'寄存器值 Q':<12} {'说明'}")
    print("-" * 55)

    for cycle in range(10):
        data = test_data[cycle]

        # 阶段 1:时钟低电平 (clk=0),寄存器值不应改变
        reg.update(data, 0)

        # 阶段 2:时钟上升沿 (clk 从 0→1),锁存数据
        result = reg.update(data, 1)

        data_bin = f"{data:04b}"
        result_bin = f"{result:04b}"
        note = "← 上升沿锁存" if result == data else "状态未变"

        print(f"{cycle:<6} {'0→1':<10} {data}({data_bin}){'':<4} {result}({result_bin}){'':<4} {note}")

    print("-" * 55)
    print()

    # ============================================================
    # 额外演示:数据只在上升沿锁存,其他时间保持
    # ============================================================
    print("=" * 55)
    print("  演示 2:非上升沿时,数据变化不被锁存")
    print("=" * 55)

    reg2 = Register4Bit()
    # 先写入初始值 5
    reg2.update(5, 0)
    reg2.update(5, 1)
    print(f"上升沿写入 5 → 寄存器值: {reg2.read()}")

    # clk 保持高电平,改变数据为 10——不应被锁存
    reg2.update(10, 1)
    print(f"clk=1 时改数据为 10 → 寄存器值: {reg2.read()} (应仍为 5)")

    # clk 回到低电平,再给上升沿写入 10
    reg2.update(10, 0)
    result = reg2.update(10, 1)
    print(f"clk 0→1 时写入 10 → 寄存器值: {reg2.read()} (现在是 10)")

    print()
    print("=" * 55)
    print("  演示完成!runoob - 理解触发器与寄存器")
    print("=" * 55)

交互演示:D 触发器时序图

下方用 Chart.js 的 stepped:true 选项绘制了时钟信号(方波)和数据信号的时序图。红色竖线标注了每个时钟上升沿(CLK 从 0 跳变到 1 的时刻),这些时刻正是 D 触发器锁存数据的关键瞬间。

D 触发器时序图 —— 上升沿锁存演示

时钟 CLK(方波) 数据输入 D 输出 Q(锁存后的值)
红色虚线 = 上升沿时刻(D 触发器锁存数据)。
Q 输出只在上升沿更新,其余时间保持不变。