深入理解计算机系统(第二版)笔记
复习《深入理解计算机系统(第二版)》顺道将一些知识点和笔记进行整理。
第四章 处理器体系结构
·指令集体系结构(ISA,Instruction-Set Architecture):一个处理器支持的指令和指令的字节级编码。
(不同处理器“家族”都有不同的ISA。一个程序编译成在一种机器上运行,就不能在另一种机器上运行。)
·硬件控制语言(HCL,Hardware Control Language):一种描述硬件系统控制部分的简单语言。
4.1 Y86指令集体系结构
4.1.1 程序员可见的状态
("程序员"既可以是用汇编代码写程序的人,也可以是产生机器代码的汇编器。)
- Y86处理器有8个寄存器:
寄存器%esp被入栈、出栈、调用和返回指令作为栈指针。其他情况中,寄存器没有固定的含义或固定值。
处理器的每个程序寄存器存储一个字。 - 有三个一位的条件码:ZF、SF和OF。
用来保存最近的算术或逻辑指令所造成的影响的有关信息。 - 程序计数器(PC):存放当前正在执行指令的地址。
- 存储器DMEM:保存着程序和数据。
(Y86程序用虚拟地址来引用存储器位置) - 状态码Stat:表明程序执行的总体状态。
(它会指示是正常运行,还是出现了某种异常)
4.1.2 Y86指令
Y86指令集:(左边是指令的汇编码表示,右边是字节编码)
注意:
- 传送指令不允许从一个存储器地址直接传送到另一个存储器地址。
- 传送指令不允许将立即数传送到存储器。
- 4个整数操作指令只对寄存器数据进行操作,还会设置3个条件码ZF、SF和OF(零、符号和溢出)。
- 7个跳转指令一句分支指令的类型和条件码的设置来选择分支 。
- 6个条件传送指令只有当条件码满足所需要的约束时,才会更新目的寄存器的值。
- call指令将返回地址入栈,如何跳到目的地址。
- ret指令从过程调用中返回。
- pushl指令实现入栈。
- popl指令实现出栈。
- halt指令停止指令的执行。
4.1.3 指令编码
- 每条指令的第一个字节表明指令的类型。
这个字节分为两部分,每部分4位:高4位时代码(code)部分,低4位是功能(function)部分。 - 整数操作、条件传送和分支指令的具体编码:
- 8个程序寄存器中每个都有相应的0~7的寄存器标识符(register ID)。
- 程序寄存器存在CPU中的一个寄存器文件中,这个寄存器文件就是一个小的、以寄存器ID作为地址的随机访问存储器。
- 当需要指明不应访问任何寄存器时,就用ID值0xF来表示。
- 有的指令可能有附加的寄存器指示符字节(register specifier byte)
5.有些指令需要一个附加的4字节常数字(constant word)。
所有整数采用小端法(little-endian)编码,当指令按照反汇编格式书写时,这些字节就以相反的顺序出现。
irmovl $15,%ebx
#Y86指令编码:30 f3 0f 00 00 00
4.1.4 Y86异常
状态码Stat:描述程序执行的总体状态。
- 代码值为1,命名为AOK,表示程序执行正常。
(任何AOK意外的代码都会使处理器停止) - 代码值为2,命名为HLT,表示处理器执行了一条halt指令。
- 代码值为3,命名为ADR,表示处理器试图从一个非法储存器地址读或者像一个非法储存器地址写。
- 代码值为4,命名为INS,表示遇到了非法的指令代码。
4.1.5 Y86程序
为了简化,Y86没有遵守IA32将有些寄存器指定为被调用者保存寄存器的规则。
- “.”开头的词是汇编器命令(assembler directive):告诉汇编器调整地址
- 第2行:命令.pos0告诉编译器应该从地址0处开始产生代码。
- 第3行和第4行:初始化栈指针和帧指针。
- 第46行:.pos命令知名地址0x100.
- 第47行:声明了标号Stack通过第46行来指明地址。
- 第9~13行:声明了一个4个元素的数组。(用.align命令指定在四字节边界处对齐)
- 第15~24行:给出了“main”过程。
YAS汇编器对上述代码的汇编结果:
4.1.6 一些Y86指令的详情
pushl %esp
执行该指令时,处理器的行为是不确定的。原因是:入栈的寄存器会被同一条指令修改。
通常会有两种约定: |
---|
1)压入%esp的原始值 |
2)压入减去4的%esp的值 |
4.2 逻辑设计和硬件控制语言HCL
4.2.1 逻辑门
- 逻辑门是数字电路的基本计算元素。它们产生的输出,等于它们输入位值的某个布尔函数。
- 逻辑门总是活动的。
(一旦一个门的输入变化了,在很短的时间内,输出就会相应地变化。)
逻辑门的类型:
4.2.2 组合电路和HCL布尔表达式
- 组合电路(combinational circuits):讲很多的逻辑门组合成一个网,就能构建计算块(computational block)。
构建这些网的限制: |
---|
两个或多个逻辑门的输出不能连接在一起 |
这个网必须是无环的 |
- 检测位相等的组合电路的HCL语言:
bool eq = (a && b) || (!a && !b)
信号xor的HCL语言:
bool xor = (!a && b) || (a && !b)
- 多路复用器(multiplexor,“MUX”):根据输入控制信号的值,从一组不同的数据信号中选出一个。
bool out = (s && a) || (!s && b)
控制信号是输出位s |
---|
当s为0时,输出等于b |
当s为1时,输出等于a |
注意:
- 电路的输入变化了,在一定的延迟之后,输出也会相应的变化。
- 逻辑门只对位值0和1进行操作。
- 组合逻辑没有部分求值这条规则,逻辑门只是简单地响应输入的变化。
4.2.3 字级的组合电路和HCL整数表达式
- 通过将逻辑门组合成大的网,可以构造出能计算更加复杂函数的组合电路。
- 执行字级计算的组合电路根据输入字的各个位,用逻辑门来计算输出字的各个位。
bool Eq =( A == B )
注意:练习题4.9答案的图有错误
- 字级多路复用器
int Out = [
s: A;
1: B;
]
(当控制信号s为1时,输出会等于输入字A,否则等于B)
int Out4 = [
!s1 && !s0 : A; # 00
!s1 : B; # 01
!s0 : C; # 10
1 : D; # 11
]
(任何以#开头到行尾结束的文字都是注释)
int Min3 = [
A <= B && A<=C : A;
B <= A && B<=C : B;
1 : C;
]
- 算术/逻辑单元(ALU)
ALU中的四个操作对应于Y86指令集支持的四种不同的整数操作,且控制值和操作的功能码对应。
4.2.4 集合关系
从一个两位信号code中选择来为四路复用器产生信号s1和s0:
用两位的信号code可以控制对4个数据字A、B、C和D做选择。根据可能的code值,可以用相等测试来表示信号s1和s0的产生:
bool s1 = code == 2 || code == 3;
bool s0 = code == 1 || code == 3;
#更简洁的方式来表示:
bool s1 = code in { 2, 3 };
bool s0 = code in { 1, 3 };
4.2.5存储器和时钟
- 时序电路(sequential circuit):有状态并且在这个状态上进行计算的系统。
- 存储设备都是由同一个时钟控制。(时钟是一个周期性信号,决定什么时候要把新值加载到设备中。)
- 时钟寄存器:存储单个位或字。
- 随机访问存储器:存储多个字。
- 寄存器在大多数时候都保持在稳定状态,产生的输出等于它的当前状态。
当时钟变成高电位的时候,输入信号就加载到寄存器中,称为下一个状态y。
Y86处理器会用时钟寄存器保存程序计数器(PC),条件代码(CC)和程序状态(Stat)。 - 寄存器文件
- 寄存器文件有两个读端口(A和B),还有一个写端口(W)。
多端口→同时进行多个读和写操作→电路可以同时读两个程序寄存器的值和更新第三个寄存器的状态。 - 寄存器文件读数据类似于:一个以地址为输入、数据为输出的一个组合逻辑块
- 像寄存器文件写入字是由时钟信号控制的(类似于将值加载到时钟寄存器)。
- 随机访问存储器
- 有一个地址输入,一个写的数据输入,以及一个读的数据输出。
- 如果地址合法,就会执行相应的操作,否则error信号会被设置为1。
4.3 Y86的顺序实现
4.3.1 将处理组织成阶段
- 通常处理一条指令包括很多操作。关于各个阶段以及各阶段内执行操作的描述:
阶段 | 执行操作 | 值 |
---|---|---|
取指(fetch) | 取指阶段从存储器读取指令字节,地址为程序计数器(PC)的值。 | icode(指令代码)、ifun(指令功能)、(rA、rB、valC)、valP |
译码(decode) | 从寄存器文件读入最多两个操作数。 | valA和/或valB |
执行(execute) | 算术/逻辑单元(ALU)①执行指令指明的操作;②计算存储器引用的地址;③增/减栈指针 | valE |
访存(memory) | 从存储器读/写数据。 | (valM) |
写回(write back) | 最多可以写两个结果到寄存器文件。 | |
更新PC(PC update) | 将PC设置成下一条指令的地址 |
2.Y86指令序列示例如下。后续将描述不同Y86指令在各阶段时怎么处理的。
3.对OPl(整数和逻辑运算)、rrmovl(寄存器-寄存器传送)和irmovl(立即数-寄存器传送)类型的指令所需的处理。
符号M1[X]表示访问(读或者写)存储器位置X出的一个字节,而M4[X]表示访问四个字节。
- 下面是一条具体的subl指令的处理过程的例子:
- 下面是一条具体的irmovl指令的处理过程的例子:
- 对存储器读写类型指令rmmovl和mrmovl所需的处理。
- 下面是一条具体的rmmovl指令的处理过程的例子:
- 处理pushl和popl指令所需的步骤。
(最难实现的Y86指令,既涉及访问存储器,又要增加或减少栈指针)
- 下面是一条具体的pushl指令的处理过程的例子:
- 下面是一条具体的popl指令的处理过程的例子:
- 对三类控制转移的处理:各种跳转、call和ret。
不需要寄存器指示符字节
- 下面是一条具体的je指令的处理过程的例子:
jXX指令在执行阶段需要检查条件码和跳转条件来确定是否要选择分支,产生出一个一位信号Cnd。
- 下面是一条具体的call指令的处理过程的例子:
- 下面是一条具体的ret指令的处理过程的例子:
- 注意:ret指令和popl指令在执行阶段都要从寄存器%esp中读取valA和valB
4.3.2 SEQ硬件结构
- 实现所有Y86指令所需要的计算可以组织成六个基本阶段:取指、译码、执行、协会和更新PC。
- 一个能执行这些计算的硬件结构的抽象表示:
硬件单元与各个处理阶段相关联:
阶段 | 硬件单元与其的相关 |
---|---|
取指 | ①将程序计数器寄存器作为地址,指令存储器读取指令的字节;②PC增加器(PC incrementer)计算valP,即增加了的程序计数器。 |
译码 | 寄存器文件有两个读端口A和B,从这两个端口同时读寄存器值valA和valB。 |
执行 | ①执行阶段会根据指令的类型,将算术/逻辑单元(ALU)用于不同的目的。②条件码寄存器(CC)有三个条件码位。ALU负责计算条件码的新值。 |
访存 | 在执行访存操作时,数据存储器读出或写人一个存储器字。 |
写回 | 寄存器文件有两个写端口。①端口E:写ALU计算出来的值②端口M:写从数据存储器读出的值。 |
4.3.3 SEQ的时序
- 组合逻辑不需要任何时许或控制。
- 指令存储器可以看成是组合逻辑(指令存储器只用来读指令)。
剩余的四个硬件单元 | 对它们的时序进行明确的控制 |
---|---|
程序计数器 | 每个时钟周期,程序计数器都会装载新的指令地址。 |
条件码寄存器 | 只有在执行整数运算指令时,才会装载条件码寄存器。 |
数据存储器 | 只有在执行rmmovl、push1或call 指令时,才会写数据存储器。 |
寄存器文件 | 寄存器文件的两个写端口允许每个时钟周期更新两个程序寄存器(oxf作为端口地址表示不执行写操作) |
- Y86指令集的本质遵循以下原则组织计算:
处理器从来不需要为了完咸一条指令的执行而去读由该指令更新了的状态。
例子1:
pushl指令在执行过程中得到信号valE后,不是将valE先写入寄存器,再根据更新后的寄存器寻址,而是将信号valE既作为寄存器写的数据,也作为存储器写的地址。
例子2:
再举个例子来说明这条原则,我们可以看到有些指令(整数运算)会设置条件码,有些指令(跳转指令)会读取条件码,但没有指令必须既设置又读取条件码。虽然要到时钟上升开始下一个周期时,才会设置条件码,但是在任何指令试图读之前,它们都会更新。
- 每个周期开始时,==状态元素(程序计数器、条件码寄存器、寄存器文件以及数据存储器)是根据前一条指令设置的。==信号传播通过组合逻辑,创建出新的状态元素的值。在下一个周期开始时,这些值会被加载到状态元素中
4.3.4 SEQ阶段的实现
- 控制逻辑中必须被显式引用的常数:
(指令、功能码、寄存器ID、ALU操作和状态码的编码)
- 取指阶段
- | need_regids | need_valC |
---|---|---|
INOP | — | — |
IHAL | — | — |
IRRMOVL | √ | — |
IIRMOVL | √ | √ |
IRMMOVL | √ | √ |
IMRMOVL | √ | √ |
IOPL | √ | — |
IJXX | — | √ |
ICALL | — | √ |
IRET | — | — |
IPUSHL | √ | — |
IPOPL | √ | — |
bool need_regids =
icode in { IRRMOVL , IIRMOVL , IRMMOVE ,
IMRMOVL , IOPL , IPUSHL , IPOPL};
bool need_valC =
icode in { IIRMOVL , IRMMOVE ,IMRMOVL ,
IJXX , ICALL };
- 译码和写回阶段
读端口 | 写端口 |
---|---|
srcA | dstE |
srcB | dstM |
- | srcA | srcB | dstE | dstM |
---|---|---|---|---|
INOP | — | — | — | — |
IHAL | — | — | — | — |
IRRMOVL | valA⬅R[rA] | — | R[rB]⬅valE | — |
IIRMOVL | — | — | R[rB]⬅valE | — |
IRMMOVL | valA⬅R[rA] | valB⬅R[rB] | — | — |
IMRMOVL | — | valB⬅R[rB] | — | R[rA]⬅valM |
IOPL | valA⬅R[rA] | valB⬅R[rB] | R[rB]⬅valE | — |
IJXX | — | — | — | — |
ICALL | — | valB⬅R[%esp] | R[%esp]⬅valE | — |
IRET | valA⬅R[%esp] | valB⬅R[%esp] | R[%esp]⬅valE | — |
IPUSHL | valA⬅R[rA] | valB⬅R[%esp] | R[%esp]⬅valE | — |
IPOPL | valA⬅R[%esp] | valB⬅R[%esp] | R[%esp]⬅valE | R[rA]⬅valM |
- srcA的HCL描述:
注意:①IRMMOVL指令需要读valA,但IMRMOVL指令不需要读valA②只有popl指令会同时用到寄存器文件的两个写端口
# Code from SEQ
int srcA = [
icode in { IRRMOVL,IRMMOVL,IOPL,IPUSHL} : rA;
icode in { IPOPL,IRET }: RESP ;
1 : RNONE; # Don't need register
];
- srcB的HCL描述:
注意:IRRMOVL、IIRMOVL指令不需要读valA
# Code from SEQ
int srcB = [
icode in { IRMMOVL,IMRMOVL,IOPL } : rB;
icode in { ICALL , IPOPL,IRET ,IPUSHL}: RESP ;
1 : RNONE; # Don't need register
];
- dstE的HCL描述:
(写端口E的目的寄存器)
int dstM = [
icode in { IRRMOVL } :rB;
icode in { IIRMOVL,IOPL } : rB;
icode in { IPUSHL,IPOPL,ICALL,IRET }: RESP;
1 : RNONE ; # Don't write any register
];
- dstM的HCL描述:
(写端口M的目的寄存器)
int dstM = [
icode in { IMRMOVL , IPOPL } :rA ;
1 : RNONE ; # Don't write any register
];
- 执行阶段
- | aluA | aluB | set_cc |
---|---|---|---|
INOP | — | — | — |
IHAL | — | — | — |
IRRMOVL | valA | 0 | — |
IIRMOVL | valC | 0 | — |
IRMMOVL | valC | valB | — |
IMRMOVL | valC | valB | — |
IOPL | valA | valB | √ |
IJXX | — | — | — |
ICALL | -4 | valB | — |
IRET | 4 | valB | — |
IPUSHL | -4 | valB | — |
IPOPL | 4 | valB | — |
- aluA的HCL描述:
int aluA = [
icode in { IRRMOVL , IOPL } : valA ;
icode in { IIRMOVL , IRMMOVL , IMRMOVL } : valC;
icode in { ICALL , IPUSHL } : -4;
icode in { IRET , IPOPL } :4;
# Other instructions don't need ALU
];
- aluB的HCL描述:
int aluB = [
icode in { IRMMOVL , IMRMOVL , IOPL , ICALL,
IPUSHL , IRET , IPOPL } : valB;
icode in { IRRMOVL , IIRMOVL } : 0;
# Other instructions don't need ALU
];
- ALU控制的HCL描述:
(OPL指令使用指令ifun字段中编码的操作)
int alufun = l
icode == IOPL : ifun;
1 : ALUADD;
];
- set_cc的HCL描述:
(用信号set_cc来控制是否应该更新条件码寄存器)
bool set_cc = icode in { IOPL };
4.访存阶段
- | men_addr | men_data | men_read | men_write |
---|---|---|---|---|
INOP | — | — | — | — |
IHAL | — | — | — | — |
IRRMOVL | — | — | — | — |
IIRMOVL | — | — | — | — |
IRMMOVL | valE | valA | — | √ |
IMRMOVL | valE | — | √ | — |
IOPL | — | — | — | — |
IJXX | — | — | — | — |
ICALL | valE | valP | — | √ |
IRET | valA | — | √ | — |
IPUSHL | valE | valA | — | √ |
IPOPL | valA | — | √ | — |
- men_addr的HCL描述:
(存储器读和写的地址)
int mem_addr = [
icode in { IRMMOVL , IPUSHL , ICALL , IMRMOVL } : valE;
icode in { IPOPL , IRET } : valA;
# Other instructions don't need address
];
- men_data的HCL描述:
(存储器写的数据)
int mem_data = [
# value from register
icode in { IRMMOVL , IPUSHL } : valA;
# Return PC
icode == ICALL : valP;
# Default: Don't write anything
];
- men_read的HCL描述:
(是否需要从存储器读数据)
bool men_read = icode in { IMRMOVL , IPOPL , IRET };
- men_write的HCL描述:
(是否需要向存储器写数据)
bool men_write = icode in { IRMMOVL , IPUSHL , ICALL };
- Stat的HCL描述:
(访存阶段最后的功能是根据取值阶段产生的icode、imem_error、instr_valid值以及数据存储器产生的dmem_error信号,从指令执行的结果来计算状态码stat。)
## Determine instruction status
int stat = [
imem_error || dmem_error : SADR; #地址异常状态码
! instr_valid : SINS; #非法指令状态码
icode == IHALT : SHLT; #halt状态码
1 : SAOK ; #SAOK:正常操作状态码
];
- 更新PC阶段
- | new_PC |
---|---|
INOP | valP |
IHAL | valP |
IRRMOVL | valP |
IIRMOVL | valP |
IRMMOVL | valP |
IMRMOVL | valP |
IOPL | valP |
IJXX | Cnd?valC:valP |
ICALL | valC |
IRET | valM |
IPUSH | valP |
IPOPL | valP |
- new_PC的HCL描述:
int new_pc = [
# Call. Use instruction constant
icode == ICALL : valC;
#Taken branch. Use instruction constanticode =IJXX && Cnd : valC;
# Completion of RET instruction. Use value from stack
icode == IRET : valM;
#Default: Use incremented PC
1 : valP;
];
汇总
- | need_regids | need_valC | srcA | srcB | dstE | dstM | aluA | aluB | set_cc | men_addr | men_data | men_read | men_write | new_PC |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
INOP | — | — | — | — | — | — | — | — | — | — | — | — | — | valP |
IHAL | — | — | — | — | — | — | — | — | — | — | — | — | — | valP |
IRRMOVL | √ | — | valA⬅R[rA] | — | R[rB]⬅valE | — | valA | 0 | — | — | — | — | — | valP |
IIRMOVL | √ | √ | — | — | R[rB]⬅valE | — | valC | 0 | — | — | — | — | — | valP |
IRMMOVL | √ | √ | valA⬅R[rA] | valB⬅R[rB] | — | — | valA | valB | — | valE | valA | — | √ | valP |
IMRMOVL | √ | √ | ==— == | valB⬅R[rB] | — | R[rA]⬅valM | valC | valB | — | valE | — | √ | — | valP |
IOPL | √ | — | valA⬅R[rA] | valB⬅R[rB] | R[rB]⬅valE | — | valA | valB | √ | — | — | — | — | valP |
IJXX | — | √ | — | — | — | — | — | — | — | — | — | — | — | Cnd?valC:valP |
ICALL | — | √ | — | valB⬅R[%esp] | R[%esp]⬅valE | — | -4 | valB | — | valE | valP | — | √ | valC |
IRET | — | — | valA⬅R[%esp] | valB⬅R[%esp] | R[%esp]⬅valE | — | 4 | valB | — | valA | — | √ | — | valM |
IPUSHL | √ | — | valA⬅R[rA] | valB⬅R[%esp] | R[%esp]⬅valE | — | -4 | valB | — | valE | valA | — | √ | valP |
IPOPL | √ | — | valA⬅R[%esp] | valB⬅R[%esp] | R[%esp]⬅valE | R[rA]⬅valM | 4 | valB | — | valA | — | √ | — | valP |
- SEQ总结
- SEQ的实现
·每条指令:分解为一系列简单的步骤
·每条指令遵循相同的指令流程框架
·将registers,memories,已定义块等硬件单元组合起来,通过控制逻辑互连 - SEQ的缺陷
·太慢,不实用
·一个时钟周期内,必须通过整个指令流,完成全部工作
·时钟周期必须足够慢
·很多硬件单元多数时间是空闲的