第四章 处理器体系结构
4.1 Y86-64 指令集体系结构
4.1.1 可见状态
同x86-64样,Y86-64的程序可以访问和修改程序寄存器、条件码、程序计数器(PC)和内存。状态码指明程序运行情况,包括运行状态和特殊事件。
内存从概念上来说就是一个很大的字节数组,保存着程序和数据。
4.1.2 Y86-64指令
只包括8字节整数操作,可以只称为“字(word)”
- 两个内存传送指令中的内存引用方式是简单的基址和偏移量形式,但是不支持第二变址寄存器和任何寄存器值的伸缩
不允许从一个内存地址直接传送到另一个内存地址。另外,也不允许将立即数传送到内存。 - 4个整数操作指令:addq、subq、andq、xorq只对寄存器数据进行操作。x86额外允许对内存数据进行操作,同时设置条件码ZF、SF、OF
- 7个跳转指令根据指令类型和条件码选择分支
- 6个条件传送指令,当条件码满足时更新
- ca11指令将返回地址入栈,然后跳到目的地址。ret指令从这样的调用中返回。
- halt 指令停止指令的执行,导致处理器停止,并将状态码设置为HLT
4.1.3 指令编码
每条指令的第一个字节表明指令的类型,高四位是代码(code)部分,第四位是功能(function)部分
rrmovq与条件传送有同样的指令代码。可以把它看作是一个“无条件传送”,就好像jmp指令是无条件跳转一样,它们的功能代码都是0。
15个程序寄存器中每一个都对应一个寄存器标识符(register ID)
程序寄存器存在CPU中的一个寄存器文件中,这个寄存器文件就是一个小的、以寄存器ID作为地址的随机访问存储器。
- 如果需要操作数的指令编码会更长一些,比如说:寄存器指示符字节(register spectifier byte),用来指定一两个寄存器。根据指令类型,指令可以指定用于数据源和目的的寄存器,或是用于地址计算的基址寄存器。
- 没有寄存器操作数的指令,例如分支指令和ca11指令,就没有寄存器指示符字节。
- 只需要一个寄存器操作数的指令(irmovq、pushq和popg)将另一个寄存器指示符设为0xE。
- 有的指令需要附加4字节的常数字,作为立即数数据(如irmovq)、地址指示符的偏移量、分支指令和调用指令的目的地址
注意:分支指令和调用指令的目的是一个绝对地址
指令集的一个重要性质就是字节编码必须有唯一的解释。任意一个字节序列要么是一个唯一的指令序列的编码,要么就不是一个合法的字节序列。
RISC与CISC
4.1.4 Y86-64 异常
状态码中除了AOK以外的任何代码都会使处理器停止
4.1.5 Y86-64 程序
x86-64代码是由GCC编译器产生的。Y86-64代码与之类似,但有以下不同点:
- Y86将常数加载到寄存器,因为不能使用立即数
- 从内存中读取数值并将其与寄存器相加,Y86需要两条指令而X86只需要一条add(因为Y86 add只能对寄存器操作)
- Y86中手工写的subq同时设置了条件码,因此testq就不是必需的;
- Y86必须用andq指令在进入循环前设置条件码
汇编器伪指令(assembler directives):
它们告诉汇编器调整地址,以便在那儿产生代码或插人一些数据。
伪指令.pos0(第2行)告诉汇编器应该从地址0处开始产生代码。这个地址是所有Y86-64程序的起点。接下来的一条指令(第3行)初始化栈指针。我们可以看到程序结尾处(第40行)声明了标号stack,并且用一个.pos伪指令(第39行)指明地址0x200。因此栈会从这个地址开始,向低地址增长
顺带一提,创建Y86代码的唯一工具是汇编器
4.1.6 详情
pushq会将栈指针减8,并将一个寄存器的值写入内存中
那么当使用pushq %rsp会出现未定义的情况(练习题4.7)
popq会将栈指针加8,并从内存中读出一个值传入寄存器
同样popq %rsp会出现未定义的情况(练习题4.8)
4.2 逻辑设计和硬件控制语言HCL
4.2.3 字级的组合电路和 HCL 整数表达式
通过设计对数据字(word)进行操作的电路
4.2.4 集合关系
其实是地址译码来控制选择器
4.2.5 存储器与时钟
时序电路基础
- 时钟寄存器(简称寄存器)存储单个位或字。时钟信号控制寄存器加载输入值。
- 随机访问存储器(简称内存)存储多个字,用地址来选择该读或该写哪个字。随机访问存储器的例子包括:
1)处理器的虚拟内存系统,硬件和操作系统软件结合起来使处理器可以在一个很大的地址空间内访问任意的字;
2)寄存器文件,在此,寄存器标识符作为地址。在IA32或Y86-64处理器中,寄存器文件有15个程序寄存器(号
rax~号r14)。
大多数时候,寄存器都保持在稳定状态(用x表示),产生的输出等于它的当前状态。
一个新的寄存器输入(用y表示),但只要时钟是低电位的,寄存器的输出就仍然保持不变。当时钟变成高电位的时候,输人信号就加载到寄存器中,成为下一个状态 y,直到下一个时钟上升沿
总结:每当每个时钟到达上升沿时,值才会从寄存器的输入传送到输出。
从寄存器文件读数据就好像它是一个以地址为输入、数据为输出的一个组合逻辑块。当srcA或srcB被设成某个寄存器ID时,在一段延迟之后,存储在相应程序寄存器的值就会出现在valA或 va1B上。例如,将src设为3,就会读出程序寄存器rbx的值,然后这个值就会出现在输出val上。
4.3 Y86的顺序实现
4.3.1 将处理组织成阶段
所有指令遵循统一的序列:
- 取指(fetch):取指阶段从内存读取指令字节,地址为程序计数器(PC)的值。从指令中抽取出指令指示符字节的两个四位部分,称为icode(指令代码)和ifun(指令功能)。
它可能取出一个寄存器指示符字节,指明一个或两个寄存器操作数指示符rA和rB。它还可能取出一个四字节常数字valc。它按顺序方式计算当前指令的下一条指令的地址 valp。也就是说,valp等于PC的值加上已取出指令的长度。
- 译码阶段从寄存器文件读人最多两个操作数,得到值 valA 和/或 valB。
通常,它读入指令rA和rB字段指明的寄存器,不过有些指令是读寄存器rsp的。
- 执行(execute):在执行阶段,算术/逻辑单元(ALU)要么执行指令指明的操作(根据ifun的值),计算内存引用的有效地址,要么增加或减少栈指针。
这个阶段检验条件码和传送条件,如果条件成立,则更新目标寄存器,同样对于一条跳转指令来说这个阶段会决定选择分支
- 访存(memory):访存阶段可以将数据写人内存,或者从内存读出数据。读出的值为 valM。
- 写回(write back):写回阶段最多可以写两个结果到寄存器文件
- 更新PC(PCupdate):将PC设置成下一条指令的地址。
Y86-64 指令 0Pq、rrmovq和 irmovq在顺序实现中的计算。
总结而言,整数操作指令的处理遵循以上模式
- 在取指阶段,我们不需要常数字,所以 valp就计算为 PC+2。
- 在译码阶段,我们要读两个操作数。在执行阶段,它们和功能指示符 ifun 一起再提供给 ALU,这样一来 valE 就成为了指令结果。
- 在写回阶段,valE被写入寄存器rB,然后PC设为valp,整个指令的执行就结束了。
以下是ALU来加valC和valB的处理得到内存操作的有效地址的操作
push和pop
跳转与返回
4.3.2 SEQ硬件结构
信息从左下角PC起,沿着线向上向右移动
硬件单元与各个处理阶段关联:
- 取指:将程序计数器寄存器作为地址,指令内存读取指令的字节。PC增加器(PCincre-menter)计算 valp,即增加了的程序计数器
- 译码:寄存器文件有两个读端口A和B,从这两个端口同时读寄存器值 valA 和 valB。
- 执行:执行阶段会根据指令的类型,将算术/逻辑单元(ALU)用于不同的目的。对整数操作,它要执行指令所指定的运算。对其他指令,它会作为一个加法器来计算增加或减少栈指针,或者计算有效地址,或者只是简单地加0,将一个输入传递到输出。
条件码寄存器(CC)有三个条件码位。ALU负责计算条件码的新值。当执行条件传送指令时,根据条件码和传送条件来计算决定是否更新目标寄存器。同样,当执行一条跳转指令时,会根据条件码和跳转类型来计算分支信号cnd。
- 访存:在执行访存操作时,数据内存读出或写人一个内存字。指令和数据内存访问的是相同的内存位置,但是用于不同的目的。
- 写回:寄存器文件有两个写端口。端口E用来写ALU计算出来的值,而端口M用来写从数据内存中读出的值。
- PC 更新:程序计数器的新值选择自:valp,下一条指令的地址;valc,调用指令或跳转指令指定的目标地址;valM,从内存读取的返回地址。
SEQ硬件结构画图惯例
4.3.3 SEQ时序
SEQ的实现包括组合逻辑和两种存储器设备:
- 时钟寄存器:程序计数器、条件码寄存器
- 随机访问存储器:寄存器文件、指令内存、数据内存
每个时钟周期,程序计数器都会装载新的指令地址。只有在执行整数运算指令时,才会装载条件码寄存器。只有在执行rmmovq、pushq或ca1指令时,才会写数据内存。
寄存器文件的两个写端口允许每个时钟周期更新两个程序寄存器。
原则:从不回读
处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态
p276/312
4.3.4 SEQ阶段的实现
设计实现SEQ所需要的控制逻辑块的HCL描述
1.取址阶段
以PC作为起始地址,从指令内存中读出10个字节。根据这些字节,我们产生出各个指令字段:
- 第一个字节被解释成指令字节,(标号为“Split”的单元)分为两个4位的数
- 标号为“icode”和“ifun”的控制逻辑块计算指令和功能码,或者使之等于从内存读出的值,或者当指令地址不合法时(由信号imem error指明),使这些值对应于nop指令
根据icode的值,计算三个一位信号
- instr_valid: 用来发现不合法的指令
- need_regids: 是否包括一个寄存器指示符字节
- need_valc: 指令是否包括一个常数字吗?
(当指令地址越界时会产生的)信号 instr_valid 和 imem_error 在访存阶段被用来
2.译码和写回阶段
寄存器文件具有4个端口,支持同时进行两个读(A、B)和两个写(E、M),每个端口都有一个地址连接(寄存器ID)和一个数据连接(一组64根线路,可读可写)
从寄存器文件中读出的值成为信号valA 和valB。两个写回值valE 和va1M作为写操作的数据
3.执行阶段
算术/逻辑单元(ALU)
根据 alufun信号的设置,对输人alua和aluB执行 ADD、SUBTRACT、AND或EXCLUSIVE-OR运算。
ALU要么为整数运算指令执行操作,要么作为加法器。根据ALU的值,设置条件码寄存器。检测条件码的值,判断是否该选择分支
4.访存阶段
读写数据:
两个控制块产生内存地址和内存输人数据(为写操作)的值。另外两个块产生表明应该执行读操作还是写操作的控制信号。
当执行读操作时,数据内存产生值 valM。
数据内存既可以写,也可以读内存的值。从内存中读出的值就形成了信号 valM
5.更新PC
产生程序计数器的新值
根据指令代码和分支标志,从信号valC、valM和 valp 中选出下一个 PC的值
6.小结
SEQ唯一的问题就是它太慢了。时钟必须非常慢,以使信号能在一个周期内传播所有的阶段。
在时钟周期起始时,从更新过的PC开始,要从指令内存中读出指令,从寄存器文件中读出栈指针,ALU将栈指针加8,为了得到程序计数器的下一个值,还要从内存中读出返回地址。
所有这一切都必须在这个周期结束之前完成。
- Title: 第四章 处理器体系结构
- Author: time will tell
- Created at : 2024-12-02 19:02:48
- Updated at : 2024-12-04 20:13:09
- Link: https://sbwrn.github.io/2024/12/02/第四章 处理器体系结构/
- License: This work is licensed under CC BY-NC-SA 4.0.