Menu Close

MIPS ADD 指令如何从寄存器流向 ALU?

CPU 内部包含许多重要组件:寄存器、ALU、控制逻辑、内存接口,以及大量辅助电路。

但是,这些组件只有连接起来、协同工作,才能真正组成一颗能够执行程序的处理器。

MIPS ADD 指令如何从寄存器流向 ALU?

CPU 内部最重要的数据连接之一,就是:

寄存器文件(Register File)与算术逻辑单元(Arithmetic Logic Unit,ALU)之间的数据通路。

寄存器文件保存 CPU 当前正在使用的数据。

ALU 则负责对这些数据执行算术和逻辑运算。

因此,一个最基本的 CPU 数据流可以表示为:

Register File

ALU

Register File

本文将跟踪一条非常简单的 MIPS 指令:

add $t0, $t1, $t2

它的含义是:

$t0 = $t1 + $t2

从汇编语言来看,这条指令非常简单。

但是在 CPU 内部,为了完成这一行代码,多个硬件模块必须精确配合。


1. 从 ADD 指令开始

先来看这条 MIPS 指令:

add $t0, $t1, $t2

这里涉及三个寄存器。

$t1 是第一个源寄存器。

$t2 是第二个源寄存器。

$t0 是目标寄存器。

概念上就是:

$t1 + $t2 → $t0

假设:

$t1 = 25

并且:

$t2 = 17

那么处理器必须计算:

25 + 17 = 42

然后把结果 42 写入:

$t0

从程序员角度来看,这就是全部操作。

但是在 CPU 内部,这个过程要复杂得多,也有趣得多。


2. CPU 需要两个源操作数

一次加法需要两个输入值。

因此,ALU 必须得到:

Operand A

和:

Operand B

对于这条指令:

add $t0, $t1, $t2

两个操作数分别来自:

$t1

和:

$t2

这也是为什么典型 MIPS 寄存器文件通常提供:

两个读端口(Two Read Ports)

这样 CPU 就可以同时读取两个寄存器。

概念上:

$t1 → Read Data 1

$t2 → Read Data 2

然后,这两个值可以同时送入 ALU。


3. 寄存器文件并不认识寄存器名称

在汇编语言中,我们使用:

$t0

$t1

$t2

这样的名字。

但是硬件并不真正理解这些名称。

MIPS CPU 内部使用的是:

寄存器编号。

在传统 MIPS 命名方式中:

$t0 = register 8

$t1 = register 9

$t2 = register 10

因此:

add $t0, $t1, $t2

对于硬件来说,实际涉及的是:

8, 9, 10

经典 MIPS 有 32 个通用寄存器。

要从 32 个寄存器中选择一个,需要:

5 bits

因为:

2^5 = 32

所以,每个寄存器编号都可以用 5 位二进制数表示。

这些 5 位寄存器地址直接编码在机器指令内部。


4. 指令本身包含寄存器地址

经典 MIPS R-type 指令长度为:

32 bits

其中主要包含以下字段:

Opcode

rs

rt

rd

shamt

funct

对于 ADD 指令:

rs

指定第一个源寄存器。

rt

指定第二个源寄存器。

rd

指定目标寄存器。

对于:

add $t0, $t1, $t2

对应关系是:

rs = $t1

rt = $t2

rd = $t0

因此,CPU 可以直接从指令中的二进制字段得到寄存器地址。

也就是说:

机器指令本身就告诉寄存器文件应该读取哪些寄存器,以及最终把结果写入哪个寄存器。


5. 两个寄存器地址进入寄存器文件

寄存器文件会接收两个读地址。

第一个来自:

rs

第二个来自:

rt

对于我们的例子:

rs → $t1

rt → $t2

这些地址进入寄存器文件后,会分别选择两个已经存储的 32 位数据。

概念上:

Read Register 1 → $t1

Read Register 2 → $t2

寄存器文件输出:

Read Data 1

和:

Read Data 2

这里最重要的一点是:

两个寄存器的数据可以同时被读取出来。


6. 为什么寄存器文件需要两个读端口

假设 CPU 的寄存器文件只有一个读端口。

执行:

$t0 = $t1 + $t2

时,就可能需要先读取 $t1,然后再读取 $t2

这样会让指令执行过程变得更加复杂。

因此,典型 MIPS 寄存器文件支持两个独立的读端口。

数据流变成:

$t1 → Read Data 1

$t2 → Read Data 2

两个值可以在基本相同的执行阶段被读取出来。

这种结构非常适合:

ADD

SUB

AND

OR

SLT

等需要两个寄存器操作数的指令。

这也说明了一个重要问题:

寄存器文件的硬件结构,与指令集的设计是密切相关的。


7. 寄存器文件把数据送入 ALU

现在来到最关键的一步。

寄存器文件的两个输出开始流向 ALU。

概念上:

Register File

Read Data 1 → ALU Input A

Read Data 2 → ALU Input B

对于:

add $t0, $t1, $t2

就是:

$t1 → ALU Input A

$t2 → ALU Input B

假设:

$t1 = 25

$t2 = 17

那么 ALU 得到:

Input A = 25

Input B = 17

现在 ALU 已经获得了执行运算所需的数据。

但是,还缺少一个非常重要的信息:

到底应该执行什么运算?


8. ALU 不只是做加法

ALU 并不是简单的加法器。

根据不同处理器设计,它通常可以执行:

Addition

Subtraction

AND

OR

XOR

Comparison

等操作。

某些处理器还可能让 ALU 负责移位操作,也可能使用独立的移位硬件。

因此,仅仅把 $t1$t2 送入 ALU 是不够的。

CPU 还必须告诉 ALU:

你现在究竟应该执行什么操作?

这就需要:

控制信号(Control Signals)。


9. 控制单元对指令进行解码

CPU 会分析机器指令中的各个字段,以确定当前到底是什么指令。

对于经典 MIPS R-type ADD 指令:

主 Opcode 表明这是一条 R-type 指令。

然后,额外的 funct 字段进一步指定具体运算。

控制逻辑对这些字段进行解码。

对于 ADD 指令,最终会生成一个 ALU 控制信号:

ALU Control = ADD

概念上:

Instruction

Control Logic

ALU Control = ADD

ALU

与此同时,寄存器文件已经把两个操作数送了过来。

因此,现在 ALU 同时知道了两件事:

数据是什么。

以及:

应该执行什么运算。


10. ALU 执行加法

ALU 接收到:

Input A = $t1 的值

Input B = $t2 的值

ALU Control = ADD

在我们的例子中:

Input A = 25

Input B = 17

ALU 内部的算术电路开始工作,最终产生:

ALU Result = 42

从更底层的硬件角度来看,这次加法最终可能由:

Full Adders

或者更先进的加法器结构实现。

因此,ALU 实际上把 CPU 的两个世界连接起来了。

底层:

Logic Gates

构成:

Adders

加法器成为:

ALU

的一部分。

然后 ALU 执行机器指令要求的算术运算。

这正是数字逻辑逐渐演变成 CPU 指令执行的过程。


11. ALU 得到结果后,工作还没有结束

ALU 已经完成:

25 + 17 = 42

但是指令还没有真正结束。

CPU 还有一个重要任务:

把结果保存下来。

对于:

add $t0, $t1, $t2

结果必须写入:

$t0

因此:

42

必须从 ALU 返回到寄存器文件。

这条路径通常属于:

Write-Back Path,写回路径。

概念上:

Register File

ALU

ALU Result

Write Back

Register File

所以,寄存器文件既是:

操作数的来源

也是:

计算结果的目的地。


12. 目标寄存器来自 rd

CPU 已经知道结果应该写到哪里。

因为目标寄存器编号已经编码在指令中。

对于 R-type ADD 指令:

目标寄存器来自:

rd

例如:

add $t0, $t1, $t2

目标是:

$t0

$t0 对应:

Register 8

因此,寄存器文件最终收到:

Write Register = 8

Write Data = 42

但是,仅仅把这些信号送到寄存器文件还不够。

CPU 还需要一个信号告诉寄存器文件:

现在允许写入。


13. RegWrite 控制寄存器更新

CPU 并不是执行任何指令时都要写寄存器。

有些指令会修改寄存器。

有些不会。

例如:

ADD 会写寄存器。

SUB 会写寄存器。

LW 会写寄存器。

但是:

SW 不会写通用寄存器。

普通分支指令通常也不会写通用寄存器。

因此,控制单元会生成一个非常重要的控制信号:

RegWrite

对于 ADD:

RegWrite = 1

它告诉寄存器文件:

把输入的数据写入指定的目标寄存器。

于是:

Write Register = $t0

Write Data = ALU Result

RegWrite = 1

现在硬件已经准备好更新 $t0


14. 寄存器读取和写入的行为不同

经典寄存器文件通常采用这样一种设计:

读取是组合逻辑的。

写入是时钟控制的。

这两者的区别非常重要。

当 Read Register 地址改变以后,对应寄存器中的数据可以通过组合逻辑传播到读输出。

通常不需要再等待另一个时钟边沿。

但是写入不同。

目标寄存器一般会在指定的有效时钟边沿真正更新。

因此,在执行:

add $t0, $t1, $t2

时,CPU 可以先读取:

$t1

和:

$t2

然后把它们送入 ALU。

ALU 完成运算后:

$t0

再在正确的时钟控制点被更新。

这种:

合数据流 + 时钟存储

的结合,是同步 CPU 设计的基础。


15. 完整的 ADD 数据通路

现在,我们可以把前面的所有步骤连起来。

从:

add $t0, $t1, $t2

开始。

指令确定:

Source 1 = $t1

Source 2 = $t2

Destination = $t0

完整数据流如下:

Instruction

Extract rs, rt, rd

Register File

Read $t1 and $t2

Read Data 1 and Read Data 2

ALU

ADD

ALU Result

Write Data

Register File

Write $t0

如果加入实际数据:

$t1 = 25

$t2 = 17

那么:

25

和:

17

进入 ALU。

ALU 计算得到:

42

然后 CPU 将:

42

写入:

$t0

到这里:

一条 ADD 指令才算真正完成。


16. 这就是数据通路 Datapath

我们前面一直在跟踪的这些硬件连接,在计算机体系结构中统称为:

Datapath,数据通路。

Datapath 包含 CPU 内部负责:

传输数据

以及:

转换数据

的硬件结构。

典型 Datapath 包括:

Register File

ALU

Multiplexers

Program Counter

Adders

Immediate-generation hardware

Memory interfaces

等等。

而控制单元则决定:

每条指令执行时,这些硬件应该如何工作。

可以用两句话理解 CPU

Datapath 负责移动和处理数据。

Control Unit 负责告诉 Datapath 应该做什么。

理解这两部分之间的区别,是学习 CPU 架构非常重要的一步。


17. 为什么很快就会需要多路选择器

我们使用的 ADD 例子非常简单。

因为 ALU 的两个操作数都直接来自寄存器。

但是,并不是所有指令都这样。

例如,有些指令需要把:

一个寄存器值

与:

一个立即数

送入 ALU。

于是 ALU 的第二个输入就出现了两个可能来源:

Register Value

或者:

Immediate Value

CPU 必须从中选择一个。

这就是:

Multiplexer,MUX,多路选择器

存在的重要原因。

同样,不同指令可能需要选择不同的:

目标寄存器

或者:

Write-Back 数据来源。

随着 CPU 支持越来越多的指令类型,Multiplexer 会大量出现在 Datapath 中。

这也是简单的:

Register File → ALU

逐渐演变成完整 CPU Datapath 的关键一步。


18. ADD 和 LW 使用 ALU 的方式不同

ALU 并不仅仅用于算术指令。

例如:

lw $t0, 8($t1)

CPU 必须先计算内存地址:

address = $t1 + 8

这个加法仍然由 ALU 完成。

但是此时,ALU 的第二个输入已经不是另一个寄存器。

而是:

Immediate Offset

也就是立即数偏移量。

因此:

ADD:

Register + Register → ALU

而 LW:

Register + Immediate → ALU

这就是为什么在典型 CPU Datapath 中,ALU 某个输入前面经常会放一个 Multiplexer。

它根据当前指令选择正确的数据来源。


19. SW 同样使用寄存器文件和 ALU

再来看:

sw $t0, 8($t1)

这条指令也需要从寄存器文件读取两个值。

但是它们的用途不同。

$t1

提供:

Base Address

而:

$t0

提供:

需要写入内存的数据。

ALU 负责计算:

$t1 + 8

得到内存地址。

与此同时:

$t0

中的数据会流向内存的:

Write Data Input

这说明一个重要事实:

寄存器文件的两个输出,并不总是承担完全相同的任务。

它们具体做什么,取决于当前正在执行什么指令。

而控制系统负责决定:

Datapath 应该如何被使用。


20. 寄存器文件处在 CPU 执行的中心

寄存器文件在 CPU 内部处于一个非常关键的位置。

大量指令都会从读取一个或多个寄存器开始。

这些数据随后可能进入:

ALU

Memory Address Generation Logic

Comparison Logic

或者:

其他 Execution Units

执行完成以后,很多结果又重新返回寄存器文件。

因此,CPU 指令执行经常呈现这样的基本模式:

Read Operands

Execute Operation

Write Result

更加具体地说:

Register File

Execution Unit

Register File

这种模式并不是 MIPS 独有的。

在现代处理器架构中,我们都可以看到类似思想。


21. 从寄存器文件到 ALU,再到 CPU

我们之前可以把 Register File 当成一个独立的硬件模块来学习。

这样可以理解 CPU 如何保存和快速访问少量但极其重要的数据。

但是,当 Register File 与 ALU 连接起来以后,它的意义就完全不同了。

现在,一个更大的 CPU 结构开始出现:

Registers 保存操作数

Register File 提供操作数

ALU 执行运算。

结果通过 Write-Back Path 返回。

Control Signals 协调整个过程。

这些硬件模块连接在一起以后,就构成了:

CPU 执行机器指令的基础。


22. 从逻辑门一直到 ADD 指令

这条数据通路还展示了计算机中非常重要的一条抽象链。

最底层是:

Transistors

晶体管构成:

Logic Gates

逻辑门构成:

Adders

加法器成为:

ALU

的一部分。

ALU 的数据来自:

Register File

Register File 与 ALU 一起执行:

Machine Instructions

而机器指令最终又构成:

Programs

因此,看起来非常简单的一条汇编语句:

add $t0, $t1, $t2

实际上连接了两个完全不同的世界:

上层的软件

和:

最底层的数字逻辑硬件。

这正是计算机体系结构最重要的思想之一。


23. 下一步是什么?

现在,我们已经把 CPU 中两个非常重要的组件连接起来:

Register File

和:

ALU

但是,这仍然只是完整处理器的一部分。

真正的 CPU 还必须回答很多问题:

指令是怎样取出来的?

CPU 怎么知道下一条应该执行什么指令?

Program Counter 在 Datapath 中处于什么位置?

Load 和 Store 指令怎样与内存通信?

Branch 指令如何改变程序执行流程?

Control Unit 如何选择正确的数据路径?

当这些组件全部连接起来以后,我们就不再是在学习几个独立的数字电路。

而是在逐渐构建:

一条完整的 CPU Datapath。


Conclusion

一条 MIPS 指令:

add $t0, $t1, $t2

从汇编语言来看非常简单。

但是在 CPU 内部,它会启动一套高度组织化的数据流。

首先,指令指定两个源寄存器:

$t1

和:

$t2

寄存器文件同时读取两个值。

然后:

Read Data 1 → ALU Input A

Read Data 2 → ALU Input B

控制逻辑告诉 ALU:

执行 ADD。

ALU 完成加法,产生结果。

随后,这个结果沿着:

Write-Back Path

返回寄存器文件。

最后,结果被写入:

$t0

因此,最核心的数据流就是:

Instruction

Register File

ALU

Write Back

Register File

这不仅仅是:

寄存器文件与 ALU 之间的一条连接。

它实际上已经是:

CPU Datapath 的起点。

一旦理解这条路径,我们就可以继续追踪一条完整的机器指令:

从 Instruction Fetch 开始,

经过 Register Access,

进入 ALU Execution,

然后可能访问 Memory

最后完成 Write Back。

而正是在这里,一个个看似独立的:

Logic Gates

Adders

Registers

ALU

Multiplexers

开始真正连接起来。

最终变成:

一颗能够执行程序的 CPU

Posted in 计算机结构教程