简介
- 从纯文本的程序到可执行的编码,以
hello.c
为例- 预处理:把头文件的内容直接插入程序文本。结果得到另一个c程序,通常以.i结尾
- 编译:编译器将本文文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序
- 汇编:汇编器将hello.s翻译成机器指令(编码),把这些指令打包成一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中(一个二进制文件)。
- 链接:hello程序可能会用到许多函数,如printf。printf函数存在与一个名为printf.o的单独编译的与编译好了的目标文件中,这个文件需要以某种形式合并到我们的hello.o程序中。链接器就负责处理这种合并
- 虚拟内存:虚拟内存是一个抽象概念,它为每个进程提供一个假象,即每个进程都在单独的使用主存。每个进程看到的内存都是一致的,称为虚拟地址空间
- 虚拟地址空间的结构:
- 程序代码和数据
- 堆:代码和数据区在进程一开始运行时就被指定了大小,与此不同,当调用像malloc和free这样的函数时,堆可以在运行时动态伸缩
- 共享库:大约在地址空间的中间部分是一块用来存放像C标准这样的共享库的代码和数据的区域
- 栈
- 内核虚拟内存:地址空间的顶部区域是为内核保留的。不允许任何程序的读写
程序结构和执行
整数运算
无符号加法
考虑两个非负整数x和y,满足$0 \leq x, y<2^w$。每个数都能表示为w为无符号数字。如果计算他们的和,我们就有一个可能的范围$0 \leq x+y \leq 2^{w+1} - 2$,表示这个和可能需要w+1位。对于固定精度的编程语言会发生溢出。
我们为x和y定义运算$+^u_w, 其中0 \leq x, y < 2^w$,该操作是把整数和x+y截断为w位得到的结果。这也可以被视为一种形式的模运算,对于x+y的位级表示,简单的丢弃任何权重大于$2^{w-1}$的位就可以计算出和模$2^w$。如考虑一个4位数字表示$x=12, y=9,x+y=21([10101])$如果丢掉最高位,我们的到[0101],也就是十进制的5。也就和值21 mod 16 = 6一致。
定义$+^u_w$:
对于满足$0 \leq x, y < 2^w$的x和y有
$$
x+^u_w y =
\begin{cases}
x+y& ,{x+y<2^w}& 正常\
x+y-2^w& ,{2^w \leq x+y < 2^{w+1}}& 溢出
\end{cases}
$$
如何判断是否发生了溢出:
- 原理:对在范围$0 \leq x, y \leq UMax_w$中的x和y,令$s = x +^u_w y$。则对计算是,当且仅当s<x(或等价地s<y)时发生溢出
- 推导:显然$x+y \geq x$,因此如果s没有溢出$s \geq x$。如果s发生溢出,有$s = x+y-2^w$,假设$y < 2^w$,有$y-2^w < 0$,因此$s = x+(y-2^w) < x$
乘以常数
整数乘法指令相当慢,需要多个时钟周期,然而其他的整数运算(如加法、减法、位移)之需要1个时钟周期。因此编译器使用了一项重要的优化:试着用移位和加法运算的组合来代替乘以常数因子的乘法。
如$x \times 14$,$14=2^3+2^2+2^1$,编译器将乘法重写为(x<<3)+(x<<2)+(x<<1),将一个乘法代替为3个移位和2个加法。甚至还可以$14=2^4-2^1$。
到这里,无符号数和补码的结果还是相同的
除以2的幂
在大多数机器上,整数除法要比整数乘法更慢:需要30个或者更多的时钟周期。除以2的幂也可以通过用移位(左移)运算来实现。
无符号和补码数分别使用逻辑移位和算数移位来达到目的:
- 逻辑移位
- 移出的空位用0补上
- 算术移位
- 对于无符号型,算术移位等同于逻辑移位
- 对于有符号型,算数左移等同于逻辑左移,算数右移补的是符号位
定义$\lfloor a \rfloor$为向下舍入,$\lceil a \rceil$为向上舍入。
对于$x \geq 0$和$y>0$,结果会是$\lfloor x/y \rfloor$,对于$x<0$和$y>0$,结果会是$\lceil x/y \rceil$。也就是说要向下舍入一个正值,而向上舍入一个负值。
- 除以2的幂的无符号数除法
- 很简单,就直接右移,产生$\lfloor x/2^k \rfloor$
- 除以2的幂的补码,正值情况,向下舍入
- 也是直接右移,直接右移的结果是向下舍入的
- 除以2的幂的补码,负值情况,向上舍入
- 不能直接右移,因为直接右移导致结果向下舍入,而我们需要向上舍入
- 执行算数右移前要加上一个适当的偏执量才能使得结果正确舍入
- 表达式:(x+(1<<k)-1)>>k产生数值$\lceil x/2^k \rceil$
- 偏置技术利用如下属性:对于整数x和y(y>0),$\lceil x/y \rceil=\lfloor (x+y-1)/y \rfloor$。这样就可以通过逻辑移位来的到向上取整的结果了
- 推导: $\lceil x/y \rceil=\lfloor (x+y-1)/y \rfloor$,设$x = qy+r$,其中$0 \leq r < y$,得到(x+y-1)/y=q+(r+y-1),因此$\lfloor (x+y-1)/y \rfloor = q + \lfloor (r+y-1)/y \rfloor$。当r=0时,后面一项等于0,当r>0时,等于1。级通过给x增加一个偏移量y-1,然后再将除法向下舍入,当y整除x时,我们得到q,否则,得到q+1。
浮点数
IEEE浮点数表示
定点表示法不能很有效地表示非常大的数字。我们希望通过给定x和y的值,来表示形如$x \times 2^y$的数。
IEEE浮点数标准用$V = (-1)^s \times M \times 2^E$的形式来表示一个数:
- 符号(sign)
- s决定是负数(s=1)还是正数
- 尾数(significand) M
- M是一个二进制小数,它的取值范围是
[1, 2)
或[0, 1)
- 有n位小数字段$frac=f_{n-1}…f_0$编码尾数M,但编码出来的值也依赖于阶码字段是否等于0(规格化的和非规格化的)
- 阶码(exponent) E
- E的作用是对浮点数加权,这个权重是2的E次幂
- 由k位的阶码字段组成$exp=e_{k-1}…f_0$来编码阶码E
- 以下是两种常见的格式:单精度和双精度
- 单精度:s、exp、frac字段分别为1位、k=8位和n=23位,得到一个32位的表示
- 双精度:s、exp、frac字段分别为1位、k=11位和n=52位,得到一个64位的表示
- 给定位的表示,根据exp的值,被编码的值可以分成三种不同的情况(最后一种情况有两种变种)
- 规格化的
- exp字段既不全为0也不全为1
- 阶码的值E=e-Bias,e是无符号数,而$Bias=2^{k-1}-1$的偏置值
- 尾数M=1+f,f是frac字段描述的小数值$0 \leq f < 1$
- 非规格化的值
- exp字段全为0时
- 阶码E=1-Bias
- 尾数M=f
- 注意和规格化的区别
- 特殊值
- 当exp全为1,frac字段全为0时,得到的值表示无穷
- 当exp全为1,frac字段不全为0时,得到的值表示NaN(Not a Number)
- 规格化的
示例:假定用6位格式表示,k=3的阶码位和n=2的尾数位,如下
描述 | 位表示 | e | E | $2^E$ | f | M | $2^E \times M$ | V | 十进制 | 公式 |
---|---|---|---|---|---|---|---|---|---|---|
0 | 0 0000 000 | 0 | -6 | $\frac1{64}$ | $\frac08$ | $\frac08$ | $\frac0{512}$ | 0 | 0.0 | E=1-Bias |
最小非规格化数 | 0 0000 001 | 0 | -6 | $\frac1{64}$ | $\frac18$ | $\frac18$ | $\frac1{512}$ | $\frac1{512}$ | 0.001953 | M=f |
0 0000 010 | 0 | -6 | $\frac1{64}$ | $\frac28$ | $\frac38$ | $\frac2{512}$ | $\frac1{256}$ | 0.003906 | ||
… | … | … | .. | … | ||||||
最大非规格化数 | 0 0000 111 | 0 | -6 | $\frac1{64}$ | $\frac78$ | $\frac78$ | $\frac7{512}$ | $\frac7{512}$ | 0.013672 | |
——– | —— | – | – | — | —- | —- | —— | —– | —– | |
最小规格化数 | 0 0001 000 | 1 | -6 | $\frac1{64}$ | $\frac08$ | $\frac88$ | $\frac8{512}$ | $\frac1{64}$ | 0.015625 | E=e-Bias |
0 0001 001 | 1 | -6 | $\frac1{64}$ | $\frac18$ | $\frac98$ | $\frac9{512}$ | $\frac9{256}$ | 0.017578 | M=1+f | |
… | … | … | .. | … | ||||||
0 1110 110 | 14 | 7 | 128 | $\frac68$ | $\frac{14}8$ | $\frac{1792}8$ | 224 | 224 | ||
最大规格化数 | 0 1110 111 | 14 | 7 | 128 | $\frac78$ | $\frac{15}8$ | $\frac{1920}8$ | 240 | 240 | |
——– | —— | – | – | — | —- | —- | —— | —– | —– | |
无穷大 | 0 1111 000 | - | - | - | - | - | - | 无穷 | - |
程序的机器级表示
数据格式
C声明 | Intel数据类型 | 汇编代码后缀 | 大小(字节) |
---|---|---|---|
char | 字节 | b | 1 |
short | 字 | w | 2 |
int | 双字 | l | 4 |
long | 四字 | q | 8 |
char* | 四字 | q | 8 |
float | 单精度 | s | 4 |
double | 双精度 | l | 8 |
访问信息
一个x86-64的CPU包含一组16个储存64位值的通用目的寄存器。每个寄存器都有特殊的用途,它们的名字就反映了这些用途。
64寄存器 | 32位寄存器 | 16位寄存器 | 8位寄存器 | 用途 |
---|---|---|---|---|
%rax | %eax | %ax | %ax | 返回值 |
%rbx | %ebx | %bx | %bl | 被调用者保存 |
%rcx | %ecx | %cx | %cl | 第4个参数 |
%rdx | %edx | %dx | %dl | 第3个参数 |
%rsi | %esi | %si | %sil | 第2个参数 |
%rdi | %edi | %di | %dil | 第1个参数 |
%rbp | %ebp | %bp | %bpl | 被调用者保存 |
%rsp | %esp | %sp | %spl | 栈指针 |
%r8 | %r8d | %r8w | %r8b | 第5个参数 |
%r9 | %r9d | %r9w | %r9b | 第6个参数 |
%r10 | %r10d | %r10w | %r10b | 调用者保存 |
%r11 | %r11d | %r11w | %r11b | 调用者保存 |
%r12 | %r12d | %r12w | %r12b | 被调用者保存 |
%r13 | %r13d | %r13w | %r13b | 被调用者保存 |
%r14 | %r14d | %r14w | %r14b | 被调用者保存 |
%r15 | %r15d | %r15w | %r15b | 被调用者保存 |
指令可以对这16个寄存器的低位字节中存放的不同大小的数据进行操作。
指令 | 功能 |
---|---|
*AX | 累加器Accumulator |
*BX | 基地址寄存器Base Register |
*CX | 计数寄存器Count Register |
*DX | 数据寄存器Data Register |
*BP | 堆栈基指针Base Pointer |
*SI/*DI | 变址寄存器Index Register |
*SP | 堆栈顶指针Stack Pointer |
*S | 段寄存器Segement Register |
MIPS体系结构
Microprocessor without Interlocked Piped Stage,流水线不会互锁的处理器。避免不同指令间的相互影响。如x86指令的标志寄存器,前一条指令作出的改动会对后面的产生影响,这MIPS所要避免的。
- 它的主要关注点
- 减少指令类型
- 降低指令复杂度
- 基本原则是用非常简单的CPU解决非常复杂的系统
- 越简单的CPU越快
- MIPS特点
- 固定指令长度(32bit)
- 简化了从存储器取指令
- 简单的寻址模式
- 简化了从存储器取操作数
- 指令数量少,功能简单
- 一条指令之完成一个操作
- 简化指令的执行过程
- 只允许Load和Store指令访问存储器
- MIPS这些特点让使用MIPS进行编程变得困难
- 固定指令长度(32bit)
基本指令
- 格式:
OPT a, b, c
- 如
add a, b, c
将b和c求和,结果存如a中 - 如此还有
sub a, b, c
mul a, b, c
div a, b, c
and a, b, c
or a, b, c
sll a, b, c
,左移srl a, b, c
,右移
- 如
指令高度统一,而且操作数都不可是存储器操作数,要使用存储器就必须使用专门的访存指令。
lw $a, $b
, load word,读取寄存器b的字(32bit),放入a中- MIPS的寄存器编号用
$
符进行标记
- MIPS的寄存器编号用
sw $a, $b
, store word,将寄存器a的字(32bit),存到b中
MIPS有32个通用寄存器,编号从0到31,可以实用$
加编号进行指示,也可使用他们的名称(每个寄存器都另有一个名称,并约定了特定的用途)
指令的基本格式
MIPS的指令非常的精简
- 按照功能划分
- 运算指令
- 访存指令
- 分支指令
- 从指令的格式划分
- R:Register,寄存器
- I:Immediate,立即数
- J:Jump,转移
R型
R | opcode | rs | rt | rd | shamt | funct |
---|---|---|---|---|---|---|
大小(bit) | 6 | 5 | 5 | 5 | 5 | 6 |
位置 | 31~26 | 25~21 | 20~16 | 15~11 | 10~6 | 5~0 |
R型指令的格式
- opcode域指定指令的类型,对于R型指令均为0
- funct来精确指定指令的类型
- rs:Source Register,通常指定第一个源操作数所在寄存器编号
- rt:Target Register,通常指定第二个源操作数所在寄存器编号
- rd:Destination Register,通常指定目的操作数所在寄存器编号
- shamt用于指定移位指令进行操作数的位数,对于非移位指令为0
通过指令得到编码,如
add $8, $9, $10
- 查MIPS的指令编码表:对于add,opcode=0, funct=32, shamt=0
- 分析指令操作数:rd=8, rs=9, rt=10
- 把对应的值转化为二进制数即可得到编码
I型
如果只用5bit来表示立即数,显然不够用,所以对于I型指令需要新的格式
I | opcode | rs | rt | immediate |
---|---|---|---|---|
大小(bit) | 6 | 5 | 5 | 16 |
位置 | 31~26 | 25~21 | 20~16 | 15~0 |
I型指令的格式
- opcode域指定指令的类型,没有funct
- funct来精确指定指令的类型
- rs:Source Register,通常指定第一个源操作数所在寄存器编号
- rt:Target Register,通常指定第二个源操作数所在寄存器编号
- immediate可以存放16位的立即数
通过指令得到编码,如
add $21, $22, -50
- opcode=8
- rs=22, rt=21, immedaite=-50
分支指令(条件指令)
- 条件分支
- 根据比较结果改变控制流
beq
(branch if equal)- opcode=4
bne
(branch if not equal)- opcode=5
- 非条件分支
- 无条件地改变控制流
j
(jump)
格式:bep reg1, reg2, L1
- 前两个是寄存器操作数,第三个是存储器地址(一个立即数,偏移量)
- 不同于x86指令,没有标志寄存器,在一条语句中完成了比较和转移
I | opcode | rs | rt | immediate |
---|---|---|---|---|
大小(bit) | 6 | 5 | 5 | 16 |
位置 | 31~26 | 25~21 | 20~16 | 15~0 |
- 如何发挥16bit的作用?
- 以当前的PC为基准,16bit偏移量可以表示$\pm 2^{15}$bytes
- 由于MIPS指令长度固定为32bit,因此我们可以用16bit的立即数来表示每4个字节为一个单位的地址,这样目标地址范围可以扩大4倍。
- 16bit偏移量可以表示$\pm 2^{17}$bytes
- 目标地址计算方法
- 分支条件不成立时,PC = PC + 4 = 下一条语句
- 分支条件成立时,PC = PC + 4 + immediate*4
对于非条件分支指令
J | opcode | immediate |
---|---|---|
大小(bit) | 6 | 26 |
位置 | 31~26 | 25~0 |
- 不同于条件分支,需要两个寄存器比较,所以可以表示更大的范围
- 目标地址计算方法:New PC = {(PC+4)后取最高4位, address, 00}
- J型指令的目标地址范围:
\pm 2^{28}
bytes- 如何到达更远的目标地址?
- 两次调用j指令
- 使用jr指令:
jr rs
,可把要转移的目标地址放在寄存器中,这样就可以使用32位的目标地址
- 如何到达更远的目标地址?
算数逻辑单元
通过MIPS指令来做示例
算数运算和逻辑运算
把编码好的指令,如add r1, r2, r3
放入IR寄存器中,根据各个域的数值控制电路向ALU发出信号:输入、输出、运算方法等。
在如对于立即数加法:addi
,ALU接受到信号,通过opcode知道要进行立即数加法,把把源寄存器作为输入,把输出放入目标寄存器。
- 立即数是16bit的,但寄存器是32bit的,要能成功相加,需要扩展16bit
- MIPS使用补码,所以这个立即数采用符号扩展,不会改变补码的值
对于逻辑运算,与算数运算类似,只是立即数采用0扩展。
门电路的基本原理
现代集成电路通常使用MOS晶体管
- N型MOS管
- Source源:电流流入
- Drain漏:电流流出
- Gate门:控制,高电频导通
- P型MOS管
- Source源:电流流入
- Drain漏:电流流出
- Gate门:控制,低电频导通
- 用这两种晶体管就构成了互补型的MOS关:CMOS
逻辑门由晶体管构成,各种逻辑门的结构这里就不说了
寄存器的原理
在MIPS结构中,寄存器是由32个位组成,从电路上来说每个bit都是一样的,都是一个 D触发器。
- D触发器
- 具有存储信息能力的基本单元
- 由若干逻辑门构成,有多种实现方式
- 主要有1个数据输入、1个数据输出和1个时钟输入
- 在上升沿,采样输入D的值,传送到输出Q,其余时间输出Q的值不变
- 要求采样信号前后有一段很短的稳定时间
用32个D触发器构成MIPS的寄存器,在将寄存器用逻辑门相连,就构成了CPU。
逻辑运算的实现
ALU中包含很多中运算单元,以与元算单元为例:32位的输入需要32个与逻辑门处理,得到对应的32位输出。ALU包含多个单元,一组输入通过不同的单元,的到多组输出,这时需要一个多选器根据选择信号(opcode)对这些输出进行选择,得到一个32位的输出。
加减法的实现
半加器
- 将两个一位二进制数相加
- 不能将进位位用与加法
全加器
- 由两个半加器组成
- 能将进位位用与加法
MIPS对溢出的处理方式
- 将操作数看做有符号数
add
和addi
- 发生溢出时会产生异常
- 将操作数看做无符号数
addu
和addiu
- 不处理溢出
- 将操作数看做有符号数
x86对溢出的处理方式
- 采用溢出标志OF,根据标志寄存器OF来进行相应的操作
对于减法运算,减法可以转换成加法,那么就要对一个数进行取反操作
- 补码表示的二进制数取反
- 规则:按位取反,末位加一
- 推导
1
2
3
4
5
6
7
8x :010101
~x:101010
----------
-1:111111
x + (~x) = -1
(~x) + 1 = -x
加法器的优化
前面将的加法有一个个的全加器串联而成,需要等待进位输入,延迟长。进位像波一样传递,这样的加法器也称为 行波进位加法器(RCA) 。
- 超前进位加法器(CLA)
- 优点:
- 计算延迟时间固定为三级门延迟
- 缺点:
- 进一步扩宽加法器的位数,则电路变得非常复杂
- 因此通常使用小规模的CLA拼接形成加法器
- 优点:
乘法器和除法器
整数乘法指令相当慢,需要多个时钟周期,然而其他的整数运算(如加法、减法、位移)之需要1个时钟周期。因此编译器使用了一项重要的优化:试着用移位和加法运算的组合来代替乘以常数因子的乘法。
如$x \times 14$,$14=2^3+2^2+2^1$,编译器将乘法重写为(x<<3)+(x<<2)+(x<<1),将一个乘法代替为3个移位和2个加法。甚至还可以$14=2^4-2^1$。
乘法器的实现
观察下列乘法过程:
1 | 1000 |
一个N位乘法器需要3个寄存器,分别设为被乘数md,乘mr,结果res。由上列过程得到启发,过程如下:
如果被乘数第一位不为0则乘以乘数
把结果加到res中
乘数右移,被乘数左移
如果执行了N次则乘法结束,否则回到第一步
对N位乘法器进行优化
- 加法移位并行
- 通过控制器,控制移位,在一个时钟周期完成移位操作
- 加法移位并行
- 减少不必要的硬件资源
- md由于左移,储存需要的位数是它原始位宽的两倍
- 取消左移
- mr右移有效数字每个周期减少1位
- 用res的低4为存放,因此可以取消mr
- res每个周期增加1位
- 增加右移,来代替md的左移,乘积始终放在高4位
- 加法器参加运输,但实际有效数字自由其位宽的一半
- md和res的高4位进行运算
- md由于左移,储存需要的位数是它原始位宽的两倍
- 减少不必要的硬件资源
除法的运算过程
观察除法的运算过程
1 | 0001 结果累加,相当于左移 |
过程如下
- 余数寄存器减去除数,检测最高位判断大小
- 除数右移
- 商左移,累加
除法器的优化:
- 硬件资源优化
- 除数寄存器实际只用了一半
- 商寄存器初始的空的,从右到左逐位填满
- 余数初始是满的,有意义的位从左到右逐位减少
- 硬件资源优化
- 性能资源优化
单周期处理器
处理器设计的主要步骤
- 分析指令系统,得出对数据通路的需求
- 为数据通路选择合适的组件
- 连接组件建立数据通路
- 分析每条指令的实现,以确定控制信号
- 集成控制信号,形成完整的控制逻辑,控制器
下面以一个简化的MIPS指令系统作为实例,改系统的指令有:
- 无符号加法和和减法
addu rd, rs, rt
subu rd, rs, rt
- 立即数的逻辑或
ori rt, rs, imm16
- 装载和储存一个字
lw rt, imm16(rs)
sw rt, imm16(rs)
- 条件分支
bep rs, rt, imm16
R | opcode | rs | rt | rd | shamt | funct |
---|---|---|---|---|---|---|
大小(bit) | 6 | 5 | 5 | 5 | 5 | 6 |
位置 | 31~26 | 25~21 | 20~16 | 15~11 | 10~6 | 5~0 |
指令位域分解
- R型指令:{op, rs, rt, rd, shamt, funct}
- I型指令:{op, rs, rt, imm16}
- 需求:
- 存放指令的存储器
- PC:存放地址的32位寄存器
通过指令操作分析需求
- 运算指令需求:
- 一组存放数据的通用寄存器,这些寄存器称为寄存器堆
- 同时读取两个寄存器的内容
- 写入一个寄存器的内容,保存结果
- 将16位的立即数扩展到32位,供I型指令需要
- 提供加、减、逻辑或功能的运算器
- 运算器的操作数可以时寄存器或立即数
- 访存指令
- 存放数据的储存器,可读可写
- 将16位的立即数扩展到32位,供I型指令需要
- 分支指令
- 比较内容,判断是否相等
- PC寄存器支持两种自增方式,加4或加一个立即数
- 运算指令需求:
整理:
- 算数逻辑单元(ALU)
- 支持运算类型
- 操作数
- 立即数扩展部件
- 支持0扩展、符号扩展
- 程序计数器(PC)
- 支持两种自增方式,加4或加一个立即数
- 寄存器堆
- 支持读操作:rs和rt
- 支持写操作:rt和rd
- 存储器
- 一个只读指令存储器
- 一个可读写的数据存储器
- 算数逻辑单元(ALU)
寄存器堆
- 内部构成
- 32个32位的寄存器
- 数据接口信号
- busA、busB:两组32位的数据输出
- busW:一组32位的数据输入
- 读写控制
- Ra(5bit):选中对应编号的寄存器,将其内容放到busA
- Rb(5bit):选中对应编号的寄存器,将其内容放到busB
- Rw(5bit):选中对应编号的寄存器,在时钟信号的上升沿,如果写使能信号有效,将busW的内容写入改寄存器
- 读操作不受时钟信号控制
- 内部构成
存储器
- 数据接口信号
- Date In:32位的数据输入信号
- Data Out:32位的数据输出信号
- 读写操作
- Address:32位的地址信号。该信号指定一个存储单元,将其内容送到数据输入信号
- Write Enable:写使能信号,在时钟上升沿,如果写使能信号有效,将数据输入信号的内容存入地址信号指定的存储单元
- 数据接口信号
数据通路的建立
- 基本原则
- 根据指令需求,连接组件,建立数据通路
- 指令的需求
- 所有指令的共同需求
- 取指令,由IFU(Instruction Fetch Unit)完成
- 程序计数器(PC)的内容是指令的地址
- 用PC的内容作为地址,访问指令存储器获得指令编码
- 更新程序计数器PC
- 顺序时PC+=4
- 分支时PC=目标地址
- 取指令,由IFU(Instruction Fetch Unit)完成
- 不同指令的各自需求
- 加减法指令的需求
- 逻辑运算指令的需求
- 访存指令需求
- 所有指令的共同需求
运算指令的控制信号
以加法运算指令为例:
- 通过IFU取指令
- 执行加法
- 解析IFU的输出,放入寄存器堆对应的输入中
- busB如果是立即数需要扩展
- 扩展又有0扩展和符号扩展
- 对ALU:给ALU信号使其执行加法计算
- 对数据存储器:如果不需要数据存储器,写使能信号关闭,防止对结果修改
- 要写入寄存器,所有要把寄存器的写使能信号设为有效
- 执行加法
- 更新PC寄存器的内容
访存指令、分支指令同理
控制信号的集成
把之前的控制信号集成起来,形成完整的控制逻辑。
流水线处理器
流水线的基本原理
执行MIPS指令的主要步骤:
- 取值
- 译码
- 执行
- 访存
- 回写
分工明确好似非常适合使用流水线,每个人专注于自己的工作,每个硬件都有使用。
我们需要将每个部分的输出放入一个流水线寄存器,这样当输出保存在流水线寄存器后,前面的寄存器就可以开始新的工作而不影响后面的工作。下一个硬件又从流水线寄存器中取上一个硬件的输入。
虽然每条指令要经历的时间不变,但是当流水线填满是,每个时钟周期都能产生一条指令,性能提升不少。
流水线的优化
如果只是简单的按照指令执行的步骤取切分流水线的话不能充分发挥流水线的优势。
时钟周期的大小取决于耗时最长的那个步骤,如果流水线平衡性较差,性能提升幅度下降。
流水线的深度并非越深越好,应用流水线寄存器也会产生时延。越深的流水线说明需要的流水线寄存器越多。单条指令的执行时间越长。
超标量流水线
除了增加流水线深度来提供速度,还有一种方式就是拓宽流水线,这样的流水线就称为超标量流水线。通常具有两条或以上并行工作的流水线称为超标量结构。也可称为超量。
流水线的”冒险”
- “冒险”(Hazard):阻止下一条指令在下一个时钟周期开始执行的情况
- 结构冒险
- 所有的硬件部件正在位之前的指令工作
- 对于只读存储器,一次只能有一个硬件进行读取
- 解决方案1:流水线停顿(不同时使用这个硬件),产生空泡(不会影响状态的值)
- 但如果等待后有与另一个硬件冲突则又要停顿,这样效率很低
- 解决方案2:指令和数据放在不同的存储器中
- 解决方案1:流水线停顿(不同时使用这个硬件),产生空泡(不会影响状态的值)
- 对于可读写的寄存器,读写同时发生显然不是一个明智的选择
- 解决方案:前半个时钟周期写,后半个读,并设置独立的读写口
- 支持这么做的原因是寄存器的读写速度较快
- 数据冒险
- 需要等待之前的指令完成数据的读写
- 如果下一条指令需要上一条指令的数据,但是上一条指令可能还没写回
- 解决方案1:停顿
- 控制冒险
- 需要根据之前指令的结果决定下一步的行为
- 类似数据冒险,指令不能确定是否发生分支,需要几个周期后才知道
- 解决方案1:停顿
- 结构冒险
数据冒险的处理
- 软件解决方案(停顿):插入nop指令,什么都不干
- 插入几条nop指令存在问题,不同的处理器的流水线深度可能不同,导致兼容。因此一般不通过软件层面解决硬件问题
- 流水线停顿,产生空泡
- 这样的空泡由硬件产生
- 检查数据冒险,如果有则插入空泡
- 数据前递(或称为旁路)
- 如加法运算,即使没有写回,它的运算结果其实已经在流水线寄存器中了,我们只需引用流水线寄存器上的值
- 但对于load指令数据前递不适用,因为load指令的值较迟产生
控制冒险的处理
- 当执行了转移指令,并确实发生转移时,产生如下的开销,称为”转移开销”
- 将按顺序预取的指令废除(排空流水线)
- 从转移目标地址重新取指定
- 转移开销的构成
- 要不要转移,判断条件引起的开销
- 转移到哪,生成目标地址引起的开销
存储层次结构
概况
- 冯诺依曼计算机结构:
- 运算器CA
- 控制器CC
- 存储器M
- 层次结构:
- 通用寄存器:CPU
- 高速缓存:SRAM
- 主存:DRAM
- 本地二级存储:Disk
- 越往上越快、容量越小价格越高
- 层次结构:
- 输入I
- 输出O
DRAM和SRAM
DRAM | SRAM | |
---|---|---|
储存单元 | 电容 | 双稳态触发器 |
集成度 | 高 | 低 |
功耗 | 低 | 高 |
价格 | 低 | 高 |
速度 | 慢 | 块 |
刷新 | 有 | 无 |
主存(内存)的工作原理
DRAM芯片以一个存储阵列位核心,通过指定行列地址取出对应的存储单元(4比特或8比特)。一个内存条往往有多个DRAM芯片(4的倍数),构成一个内存模组。从外部给入行列地址后地址会同时送到每个DRAM芯片,每个DRAM芯片同时选择储存器单元,存储单元同时输出,组成一个64/32位的数。
缓存速度很快,一般放在CPU中。CPU访问内存的过程:SDRAM(同步DRAM)的访存过程为例:
- CPU通过系统总线连接到内存控制器,内存控制器再通过存储总线连接到内存条
- CPU需要访问控制器时:
- 申请系统总线,获得总线控制权后把地址发到控制器中
- 控制器对地址分解形成行地址和列地址,然后向DRAM发起访存操作
- 预充电
- 行访问:DRAM中的行译码芯片,选出对应行,所有单元的信号放大后放在一个缓冲区
- 缓冲区信号稳定后,才能发出列地址,其中tRCD是冲行选到列选的延迟
- 列译码器接受到列地址后,从缓冲区读出对应的列,放到数据接受接口。其中从发出列地址到选出对应存储单元的数的过程称为列访问,这段时间称为CL
- 内存送出数据后,控制器采样并送到CPU
- 如果CPU的下次请求是同一行,则控制器不再发送行地址,而是直接发送列地址
- 如果下次访问不是同一行则需把缓冲区的行关闭,这个过程就叫做 预充电
- CPU需要访问控制器时:
主存技术的发展
- DDR:Double Data Rate
- 时钟的上升沿和下降沿都传输
- SDR:Single Data Rate
- 只在时钟的上升沿传输
building
高速缓存的工作原理
$$Cpu \leftrightarrow Cache \leftrightarrow Main Memory$$
- 程序的局部性原理:计算机程序从空间和时间都表现出局部性
- 时间局部性
- 最近被访问的存储器单元(指令或数据)很快会被访问
- 空间局部性
- 正在被访问的存储器单元附近的单元很快会被访问到
- 时间局部性
举个例子:
1 | for(int i=0; i<10; i++){ |
cache的基本原理
- cache对空间局部性的利用
- 从主存中取回待访问数据时,会同时取回与位置相邻的主存单元的数据
- 以数据块(Block)为单位和主存进行数据交换
- cache对时间局部性的利用
- 保存近期频繁被访问的主存单元的数据
- 这样如果cache中有数据(cache命中)则从cache中获取
- cache对空间局部性的利用
cache的写策略
- cache命中时的写策略
- 写穿透:数据同时写入cache和主存
- 写返回:数据只写入cache,仅当改数据块被替换时才写入主存
- cache失效时的写策略
- 写不分配:直接写入主存
- 写分配:将该数据所在的块读入cache后,在将数据写入cache,利用局部性原理
- cache命中时的写策略
高速缓存的设计要点
- 平均访存时间(Average Memory Access Time)=Hit Time + Miss Penalty x Miss Rate
- Hit Time:命中时间,从cache将命中数据返回的时间
- Miss Penalty:失效代价,从主存读取数据并返回的时间
- Miss Rate:失效率,未命中的概率
- 降低访存时间主要就是降低这3个参数
- cache失效的原因
- 义务失效:第一次访问
- 无法避免
- 容量失效:无法保存程序所需的所有数据块
- 可增加cache容量缓解,但会增加命中时间
- 冲突失效:多个存储器位置映射到同一个cache位置
- 需要精心设计映射策略
- 义务失效:第一次访问
- 映射策略
- 直接映射
- 多路组相联(相当于用几个表来保存,但是表越多就要用越复杂的电路判断是否在其中一个表中)
- 常见的cache替换算法
- 随机
- 轮换
- 最近最少使用(LRU):效果最好但设计复杂
存储器容量的计算
<++> | <++> |
---|---|
Ki:kibi | $1024^1$ |
Mi:mebi | $1024^2$ |
.. | .. |
中断和异常
中断向量表的结构
不同的中断产生不同的中断码,根据中断向量表查询得到对应的处理方法。
- 中断向量:中断服务程序的入口地址
- 中断向量表存放在固定的地址
- 处理方法会事先存放在中断向量对应的地址处
中断的处理过程
- 关中断
- CPU关闭中断响应,不再接受其他外部中断请求
- 关中断
- 保存断点
- 将发送中断处理的指令压入堆栈,处理完后正确返回
- 保存断点
- 识别中断源
- 确定中断类型号,从而找到相应的中断处理程序的入口地址
- 识别中断源
- 保存现场
- 保存正在处理的程序
- 保存现场
- 执行中断服务程序
- 转到中断服务程序入口开始执行,可在适当的时刻重新开放中断,以便允许相应较高优先级的外部中断
- 重新开放由标识寄存器控制
- 执行中断服务程序
- 恢复现场(8086为例)
- IRET指令(中断返回),从栈顶弹出3个字分别送入IP、CS和FLAGS寄存器
- 放在中断服务程序的末尾
- 恢复现场(8086为例)
基于中断的功能调用
- 指令
int N
- 调用n号中断,由运行指令主动触发
输入输出设备
输入输出接口的基本功能
- I/O接口改的基本功能
- 数据缓冲
- 解决CPU和外设之间的速度差距
- 提供联络信息
- 协调与同步数据交换过程
- 信号与信息格式的转换
- 模/数、数/模交换,串/并、并/串交换,电平转换
- 设备选择
- 中断管理
- 可编程功能
- 数据缓冲
输入输出接口的编址方式
- I/O端口和存储器分开编址
- I/O映像的I/O方式,I/O Mapped I/O
- x86就是这种方式
- I/O指令
- IN指令
IN AC, PORT
- 把外设端口内容输入到AL或AX
- OUT指令
OUT PORT, AC
- 把AL或AX的内容输出到外设端口
- 端口地址小于255时可以用立即数,否则需要寄存器
- IN指令
- I/O端口和存储器统一编址
- 存储器映像的I/O方式,Memory Mapped I/O
- MIPS就是这种方式
- 优点
- 可以用访问存储器的指令访问I/O端口,可以实现直接对I/O端口内的数据进行处理
- CPU中的I/O操作与访问存储器的操作统一为一套控制逻辑,简化内部结构,减少PCU引脚的数目
- 缺点
- I/O端口占用一部分存储器地址空间,使得存储器地址空间减小
- 由于利用访问存储器的指令来进行IO操作,指令的长度通常比单独IO指令要长,执行时间也较长
输入输出的控制方式
- IO控制方式的分类
- 程序控制方式
- 无条件传送方式
- 假设外设已经准备号
- CPU直接使用指令与外设传送数据
- 不查询外设的工作状态
- 程序查询传送方式
- CPU通过指令一段程序,不断查询外设的工作状态
- 握手信号,检查输入输出缓冲
- 在外设准备就绪后草进行数据传送
- CPU的不断查询浪费了很多资源
- CPU通过指令一段程序,不断查询外设的工作状态
- 无条件传送方式
- 中断控制方式
- 通过中断的方式,将数据”一块一块”地传送,当输入输出缓冲达到一定条件时,引发中断,提醒CPU的继续输入或读取等
- 优点
- CPU可和外设并行工作
- 外围设备具有申请父亲的主动权
- 一定程度上满足了IO处理的实时性要求
- 缺点
- 输入输出的数据仍由CPU承担
- 进入和退出中断增加消耗
- 直接存储器访问(DMA)方式
- 前面的方式的共同确定是数据传输仍由CPU承担,但是如果是显示器,网络等需要大量数据传输的设备就难以应对了
- Direct Memory Access(DMA)
- 数据传送过程不需要CPU干预(不需要指令程序指令)
- 由专门的硬件控制电路控制,进行外设与存储器间直接数据传送
- 该专门硬件控制电路称为DMA控制器,简称DMAC
- DMAC的基本工作步骤(以使用独立DMAC进行外设到内存传送为例)
- CPU设置DMAC内部配置寄存器
- 源地址的初始值以及传送是的地址增减方式
- 目的地址的初始值以及传送是的地址增减方式
- 待传送数据的长度
- CPU设置DMAC内部配置寄存器
- DMAC处于空闲等待状态
- IO接口向DMAC传送申请
- DMAC响应IO接口的申请
- DMAC向IO接口发起总线读传输
- DMAC向存储器发起总线写传输
- 重复5~6直到本次DMA传送完成
- 返回2,等待下一次DMA传送申请
- 通常,DMA传送完成后DMAC会通过中断信号通知CPU
- 程序控制方式