第四章 处理器体系结构
1. Y86指令集体系结构
(1)内容
定义一个指令集体系结构,包括定义各种状态元素、指令集和它们的编码、一组编程规范和异常事件处理。
(2)程序员可见状态
-
概念:Y86程序中的每条指令都会读取或修改处理器状态的某些部分,称之为处理器可见状态。
-
Y86处理器状态类似于I32。可以访问和修改程序寄存器、条件码、程序计数器和存储器,状态码指明程序是否运行正常。
RF:程序寄存器 %eax,%ecx,%edx,%ebx,%esi,%edi,%esp(出栈、入栈、调用和返回指令作为栈指针),%ebp CC:条件码 ZF、SF、OF(都是一位条件码,用来保存最近的算术或逻辑指令所造成影响的有关信息。) PC:程序计数器 存放当前正在执行的指令 DMEM:存储器 很大的字节数组,保存着程序和数据。Y86程序用虚拟地址来引用存储器位置。 Stat:程序状态码 它表明程序执行的总体状态。它会指示是正常运行还是出现了某种异常。
(3)Y86指令
-
IA32指令集的一个子集,只包括四字节整数操作。寻址方式比较少,操作也比较少。
-
指令编码长度从1个字节到6个字节不等,一条指令含有一个单字节的指令指示符,可能含有一个单字节的寄存器指示符,还可能含有一个四字节的常数字。字段fn指明是某个整数操作(OPL)、数据移动条件(cmovXX)或是分支条件(jXX)。所有数值都用十六进制表示
-
IA32的movl指令分成了4个不同的指令:irmovl、rrmovl、mrmovl和rmmovl。分别显示地指明源和目的的格式。
源操作数: 立即数i、寄存器r、存储器m 目的操作数: 寄存器r、存储器m
两个存储器传送指令中的存储器引用方式是简单的基址和偏移量形式。
- 在地址计算中,不支持第二变址寄存器和任何寄存器值的伸缩。
- 不允许从一个存储器地址直接传送到另一个存储器地址。也不允许将立即数传送到存储器。
-
4个整数操作指令
addl、subl、andl、xorl
-
7个跳转指令(jXX)
jmp、jle、jl、je、jne、jge、jg
-
有6个条件传送指令(cmovXX)
cmovle、cmovl、cmove、cmovne、cmovge、cmovg 只有当条件码满足所需要的约束时,才会更新目的寄存器的值。
-
call指令将返回地址入栈,然后跳到目的地址。ret指令从这样的过程调用中返回。
-
pushl和popl指令实现了入栈和出栈。
-
halt指令停止指令的执行。对于Y86来说,执行halt指令会导致处理器停止,并将状态码设置为HLT。
(4)指令编码
-
每条指令的第一个字节表明指令的类型。这个字节分为两个部分,每部分4位:高4位是代码部分,低4位是功能部分。功能值只有在一组相关指令共用一个代码时才有用。
- 整数操作里代码部分均为6,功能部分区分addl,subl,andl,xorl - 分支指令里代码部分均为7 - 传送指令里代码部分均为2
-
8个程序寄存器中每个都有相应的0~7的寄存器标识符。
-
程序寄存器存在一个寄存器文件中,这个寄存器文件就是一个小的、以寄存器ID作为地址的随机访问存储器。当需要指明不应访问任何寄存器时,用ID值0xF表示。
-
有的指令没有寄存器操作数(分支指令和call指令),就没有寄存器指示符字节。
-
只需要一个寄存器操作数的指令(irmovl、pushl、popl),将另一个寄存器指示符设为0xF。
-
有的指令需要一个附加的4字节常数字,可作为:irmovl的立即数数据,rmmovl和mrmovl的地址指示符偏移量,分支指令和调用指令的目的地址。
注意: - 分支指令和调用指令的目的地址是一个相对地址,而不是相对寻址方式。 - 所有整数采用小端法编码。当指令按反汇编格式书写时这些字节就以相反的顺序出现。
-
指令集的一个重要性质就是字节编码必须有唯一的解释。这个性质保证了处理器可以无二义性地执行目标代码程序。即使代码嵌入在程序的其他字节中,只要从序列的第一个字节开始处理,我们仍然可以很容易地确定指令序列。反过来说,如果不知道一段代码序列的起始位置,我们就不能准确地确定怎样将序列划分成单独的指令。
(5)Y86异常
- Y86状态码
- 出现异常时Y86处理器停止运行指令。可以调用一个异常处理程序使其更完整。
(6)Y86程序
-
Y86代码与IA32代码的主要区别:
- Y86可能需要多条指令来执行一条IA32指令所完成的功能。
- Y86没有伸缩寻址模式。
-
命令指明应该将代码或数据放在什么位置,以及如何对齐。这个程序详细说明了栈的放置、数据初始化、程序初始化和程序结束等问题。
-
以“.”开头的词是汇编命令,他们告诉汇编器调整地址,以便在那儿产生代码或插入一些数据。
-
创建Y86代码的唯一工具是汇编器。
指令集模拟器YIS - 目的:模拟Y86机器代码程序的执行,而不用试图去模拟任何具体处理器实现的行为。 - 有助于在实际硬件可用之前调试程序,也有助于检查模拟硬件或者在硬件上运行程序的结果。
(7)一些Y86指令的详情
- 大多数Y86指令是以一种直接的方式修改程序状态的。
- 执行pushl和popl指令时,处理器的行为是不确定的,因为要入栈的寄存器会被同一条指令修改。通常有两种约定:
- 压入/弹出%esp的原始值
- 压入/弹出%esp-/+4后的值
2. 逻辑设计和硬件控制语言HCL
要实现一个数字系统需要三个主要的组成部分:
- 计算对位进行操作的函数的组合逻辑
- 存储位的存储器元素
- 控制存储器元素更新的时钟信号
(1)逻辑门
-
逻辑门产生的输出,等于它们输入位值的某个布尔函数。
AND && OR || NOT ! 逻辑门只对单个位的数进行操作,而不是整个字。
-
逻辑门总是活动的,一旦一个门的输入变化,在短时间内,输出就会跟着变化。
(2)组合电路和HCL布尔表达式
-
将逻辑门组合成一个网,构建计算块(组合电路)的限制
两个以上的逻辑门的输出不能连接在一起,否则可能使线上信号矛盾,导致一个不合法的电压或电路故障。 这个网必须是无环的,否则会导致网络计算有歧义。
-
组合逻辑电路和c语言中逻辑表达式都是用布尔操作来对输入进行计算的函数。
区别:
- 组合电路的输出会持续地响应输入变化,c语言表达式只有在执行过程中被遇到才求值
- C的逻辑表达式允许参数是任意整数,0是FALSE,其他任何值0的都是TRUE,逻辑门只对位值0和1操作。
- C的逻辑表达式可能被部分求值(第一个参数就能确定结果的就不会对第二个求值)
(3)字级的组合电路和HCL整数表达式
-
执行字级计算的组合电路根据输入字的各个位,用逻辑门来计算输出字的每个位。
-
所有字级的信号都声明为int,不指定字的大小。
-
画字级电路时,中等粗度的线来表示携带字的每个位的线路,用虚线来表示布尔信号结果。
-
多路复用函数用情况表达式来描述
通用格式: [ select_1(布尔表达式):expr_1(整数表达式) select_2:expr_2 ...... select_k:expr_k ] - 从逻辑上讲,这些选择表达式是顺序求值的。 - 第一个求值为1的情况会被选中 - 选择表达式允许不互斥
-
选择表达式可以是任意的布尔表达式,可以有任意多的情况。
-
组合逻辑电路可以设计成在字级数据上执行许多不同类型的操作。
(4)集合关系
-
判断集合关系的通用格式:
iexpr in {iexpr1,iexpr2,...,iexprk} - 被测试的值iexpr和带匹配的值iexpr1~iexprk都是整数表达式。
(5)存储器和时钟
-
时序电路:有状态,且在这个状态上进行计算的系统。
-
时序电路的两类存储器设备
- 时钟寄存器(寄存器):储存单个位或字,用时钟信号控制寄存器加载输入值。 - 随机访问储存器(储存器):储存多个字,用地址选择该读/写哪个字。
-
硬件和机器级编程中的寄存器有细微差别,分为称为“硬件寄存器”和“程序寄存器”。
-
Y86处理器会用时钟寄存器保存程序计数器PC,条件代码CC和程序状态Stat。
-
处理器有一个随机访问存储器来存储程序数据。
-
处理器还包括另外一个只读存储器,用来读指令。
-
在大多数实际系统中,这两个存储器被合并为一个具有双端口的存储器:一个用来读指令,一个用来读或写数据。
3. Y86的顺序实现
SEQ 顺序处理器
每个时钟周期上,SEQ执行一条完整指令所需的所有步骤
(1)将处理组织成阶段
-
六个基本阶段
- 取指 从存储器读取指令字节,地址为程序计数器PC的值 - 译码 从寄存器读入最多两个操作数,得到valA或valB - 执行 算术/逻辑单元要么执行指令指明的操作,计算存储器引用的有效地址,要么增加或减少栈指针。得到的值为valE。 - 访存 将数据写入存储器,或从存储器中读出数据,读出的值为valM。 - 写回 最多可以写两个结果到寄存器文件 - 更新PC 将PC设为下一条指令的地址
-
各类指令及其阶段
(2)SEQ硬件结构
-SEQ抽象视图
-
程序计数器放在寄存器中
-
信息沿线流动
-
各个阶段相关的硬件单元负责执行这些处理
-
反馈线路包括要写到存储器文件的更新值,以及更新的程序计数器值
-
在SEQ中,所有硬件单元的处理都在一个时钟周期内完成
-
画图惯例
-
浅灰色方块表示硬件单元
-
控制逻辑块是用灰色圆角矩形表示的
-
线路的名字在白色椭圆中说明
-
宽度为字长的数据连接用中等粗度的线表示
-
宽度为字节或更窄的数据连接用细线表示
-
单个位的连接用虚线
(3)SEQ的时序
-
SEQ的实现包括组合逻辑和两种存储器设备
时钟寄存器 程序计数器和条件码寄存器 随机访问存储器 寄存器文件、指令存储器和数据存储器
-
组合逻辑不需要任何时序或控制
-
由于指令存储器只用来读指令,我们可以将这个单元看成是组合逻辑
-
剩下四个(程序计数器、条件码寄存器、数据存储器和寄存器文件)需要对他们的时序进行明确的控制。
条件码寄存器 只在执行整数运算指令时装载
数据存储器 只在执行rmmovl、pushl或call时写入
寄存器文件 两个写端口允许每个时钟周期更新两个程序寄存器。(特殊寄存器ID 0xF表明此端口不应执行写操作) -
组织计算原则
处理器从来不需要为了完成一条指令的执行而去读由该指令更新了的状态。
-
用时钟来控制状态元素的更新,值通过组合逻辑传播。
(4)SEQ阶段的实现
-
取指阶段
-
以PC为第一个字节的地址,一次读6个字节。
icode 控制逻辑块计算指令 ifun 功能码
-
三个一位的信号(根据icode值计算)
instr_valid 发现不合法的指令 need_regids 包含寄存器指示符字节吗 need_valC 包括常数字吗
-
后五个字节是寄存器指示符字节和常数字的组合编码。
-
-
译码和写回阶段
- 都需要访问寄存器文件,根据四个端口的情况,判断应该读哪个寄存器产生信号valA、valB。
- 寄存器文件,支持同时进行两个读和两个写,每个端口有一个地址连接(寄存器ID)和一个数据连接(32根线路),既可以作为寄存器文件的输出字,又可以作为他的输入字。
-
执行阶段
-
包括算数/逻辑单元(ALU),输出为valE信号。
ALU通常作为加法器使用
-
包括条件码寄存器
每次运行产生: 零、符号、溢出、产生信号set_cc
-
-
访存阶段
- 读或者写程序数据。
- 两个数据块产生存储器地址和存储器输入证据的值,两个产生控制信号表明应该是读还是写。当执行读操作时,数据存储器产生valM。
- 根据icode,imem_error,instr_valid,dmem_error,从指令执行的结果计算状态码Stat。
-
更新PC阶段
- 产生程序计数器的新值,依据指令的类型和是否要选择分支,新的PC可能是valC、valM或者valP。
4. 流水线的通用原理
(1)流水线化的重要特性
- 增加了系统的吞吐量,即单位时间内服务的顾客总数,不过也会轻微的增加执行时间,即服务一个用户需要的时间。
(2)处理器设计的几个重要经验:
- 管理复杂性是首要问题,尽量保持硬件设计的简单。
- 不需要直接实现ISA。
- 一开始就保证设计正确是非常重要的。
(3)PIPE-结构
-
创建一个流水线化的Y86处理器的好开端。但是,将流水线技术引入一个带反馈的系统,当相邻指令同存在相关时会导致出现问题。
-
相关的两种形式
- 数据相关 下一条指令会用到这条指令计算出的结果; - 控制相关 一条指令要确定下一条指令的位置,例如在执行跳转、调用或返回指令时,这些相关可能会导致流水线产生计算错误,称为冒险。同相关一样,冒险也可以分为两类:数据冒险和控制冒险。
(4)流水线控制逻辑
这个逻辑必须处理以下4种控制情况,这些情况是其他机制(例如数据转发和分支预测)不能能处理的:
处理ret 流水线必须暂停直到ret指令到达写回阶段。
加载/使用冒险 流水线必须暂停一个周期。
预测错误的分支 在分支逻辑发现不应该选择分支之前,分支目标处的几条指令已经进流水线了。必须从流水线中去掉这些指令。
异常 当―条指令导致异常,我们想要禁止后面的指令更新程序员可见的状态,并睏异常指令到达写回阶段时,停止执行。
实验操作
1.构建环境
cd ~/Code/shiyanlou_cs413
wget http://labfile.oss.aliyuncs.com/courses/413/sim.tar
tar -xvf sim.tar
cd sim
sudo apt-get install tk
sudo ln -s /usr/lib/x86_64-linux-gnu/libtk8.6.so /usr/lib/libtk.so
sudo ln -s /usr/lib/x86_64-linux-gnu/libtk8.6.so /usr/lib/libtk.so
make
2.汇编所有代码结果
进入测试代码 cd y86-code
进行汇编 make asuml.yo
汇编所有代码结果 make all
2.教材P239
遇到的问题
1.实验楼构建实验环境的时候输入
wget csapp.cs.cmu.edu/2e/sim/tar
一直显示
解决:有同学问了这个问题,老师把输入的地址更改了,又做了一下,可以连接了
2.YIS模拟器
下载到我电脑上的虚拟机之后make命令显示‘asuml.yo’ is up to date.
参考资料
参考资料1:深入理解计算机系统
参考资料2:(实验楼)深入理解计算机系统:实验五