冯·诺依曼模型
核心思想
程序(指令)和数据以二进制形式不加区分地存储在同一存储器中。这意味着计算机可以通过改变存储器中的内容(即程序)来改变其任务,而无需重新连接或设计硬件。这是现代计算机灵活性的基石。
五个组成部分
存储器
- 功能: 存储指令(程序)和数据。在冯·诺依曼模型中,两者没有区别,都是一串比特。
- LC-3实现: 一个由2^16个地址组成的、可寻址的内存单元,每个地址包含一个16位的字。这既是LC-3的主内存(RAM)。
- 关键点: 内存地址空间是统一的,程序计数器(PC)指向的地址就是下一条要执行的指令所在的内存地址。
处理单元
- 功能: 执行实际的算术和逻辑运算。
- LC-3实现: 算术逻辑单元(ALU)。LC-3的ALU可以执行加法、按位与、按位取反等操作。它是数据通路的核心,负责对来自寄存器或内存的数据进行计算。
输入
- 功能: 将外部世界的信息送入计算机。
- LC-3实现: 键盘。在LC-3中,键盘是通过内存映射I/O实现的。一个特定的内存地址(如
0xFE02)被关联到键盘的数据寄存器。当程序从该地址“加载”数据时,它实际上是在读取键盘的输入。
输出
- 功能: 将计算机内部的计算结果呈现给外部世界。
- LC-3实现: 显示器。同样通过内存映射I/O实现。一个特定的内存地址(如
0xFE06)被关联到显示器的数据寄存器。当程序向该地址“存储”一个字符的ASCII码时,显示器就会在屏幕上显示相应的字符。
控制单元
- 功能: 这是整个计算机的“指挥中心”。它负责协调所有其他部件的活动。
- LC-3实现: 这是本书数字电路和微架构设计的核心。
- 控制单元的操作:
- 取指: 从内存中取出由PC指向的指令,放入指令寄存器(IR)。
- 译码: 分析IR中的指令,确定需要执行什么操作(是ADD还是LD?)以及操作数在哪里。
- 执行: 根据译码结果,发出一系列精细的控制信号,来指挥数据通路、ALU和内存完成指令要求的操作。例如,对于
ADD指令,控制单元会发出信号:选择正确的寄存器作为ALU输入,设置ALU为加法模式,并将结果写回目标寄存器。
- 控制单元的操作:
指令处理周期
指令处理周期:机器的“心跳”。
控制单元通过周而复始地执行以下步骤来运行程序,这个循环就是冯·诺依曼模型的运行体现:
- 取指: 从存储器(地址为PC)中取指令,放入IR。
- 译码: 解码IR中的指令。
- 评估地址: 如果需要访问内存,计算操作数的有效地址。
- 取操作数: 从存储器或寄存器中获取执行指令所需的操作数。
- 执行: 执行指令(例如,在ALU中完成操作)。
- 存储结果: 将执行结果写回寄存器或存储器。
完成这些步骤后,PC被更新为下一条指令的地址,循环重新开始。中断的处理是这个基本循环的扩展,它允许外部事件暂停当前程序流。
时钟周期和指令周期
- 时钟周期: 硬件层面的最小时间单位,是CPU工作的节拍器。两个时钟脉冲之间的间隔就是一个时钟周期。频率为1GHz的CPU,其时钟周期为1纳秒。
- 机器指令周期: 软件层面的概念,指CPU从取出到执行完一条机器指令所需的完整时间。
核心关系:执行一条机器指令需要多个时钟周期。
示例
LD R1, [0x1234]
这条指令的意思是:将内存地址 0x1234 中的数据加载到寄存器R1中。
在一个简单的CPU设计中,这个过程需要至少4个时钟周期:
-
周期1 - 取指:
- 动作: CPU将程序计数器(PC)中的地址送到内存总线,从内存中取出
LD R1, [0x1234]这条指令本身。 - 结果: 指令被存入CPU内部的指令寄存器(IR)。时钟沿到来,这个动作结束。
- 动作: CPU将程序计数器(PC)中的地址送到内存总线,从内存中取出
-
周期2 - 译码/计算地址:
- 动作: 控制单元解析IR中的指令,识别出这是条加载指令,并计算出要访问的内存地址
0x1234。 - 结果: 地址
0x1234被准备好送到地址总线。下一个时钟沿到来,这个动作结束。
- 动作: 控制单元解析IR中的指令,识别出这是条加载指令,并计算出要访问的内存地址
-
周期3 - 内存读:
- 动作: CPU将地址
0x1234送到内存总线,并发出“读”信号。内存控制器开始工作,在经过其访问延迟后,将地址0x1234处的数据放到数据总线上。 - 结果: 数据从内存中被读出,到达CPU的数据引脚。下一个时钟沿到来,这个动作结束。 (注意:内存访问慢,这个周期可能本身就需要多个时钟周期,这里为简化视为一个)
- 动作: CPU将地址
-
周期4 - 写回:
- 动作: CPU将数据总线上的值存入目标寄存器R1。
- 结果: 指令执行完毕,R1中拥有了新数据。下一个时钟沿到来,这条指令的周期正式结束。
总结
LD R1, [0x1234](一条机器指令)- = 取指周期(1个时钟周期)
- + 译码周期(1个时钟周期)
- + 内存读周期(1个或更多时钟周期)
- + 写回周期(1个时钟周期)
- = 总共需要4个(或更多)时钟周期