0%

计算机组成与设计-硬件软件接口笔记

第一章

近几年处理器发展放缓的原因是遇到了功耗墙。功耗正相关于1/2 × 负载电容 × 电压平方 × 开关频率。
过去CPU的主频提升了1000多倍,但是功耗没有提升1000多倍的原因是电压降低了,电压和
物理工艺的关系比较大,越高的工艺电压可以做的越低,现在整体工艺发展放缓,电压相对
比较高,导致功耗越来越高。

功耗分为动态功耗和静态功耗,动态功耗受开关频率的影响,静态功耗随着电压的降低会
变大,因为电压降低漏电流增大了。目前,静态功耗占总功耗的40%。

功耗墙迫使CPU设计从单核优化走向多核设计。

第二章

  • 指令概括

可以以三种类型的指令概括下CPU的指令:1. 算数指令,2. 存储器操作指令,3. 流程控制指令。

在risc-v上,算数指令的操作数必须是寄存器或是立即数,比如一个整数加法指令的汇编可能是:

1
add x22, x22, x9 

存储器操作指令的代表就是load/store指令,比如risc-v上是:

1
2
ld x9, 8(x22)
sd x9, 96(x22)

第一条ld指令表示把x22地址偏移8Byte的地址上的数据load到x9寄存器里。
第二条sd指令表示把x9寄存器里的值存到x22地址偏移96Byte的地址上。
如上的指令里,x22叫基址寄存器,8和96叫偏移量。

流程控制指令的代表就是beq,比如risc-v上是:

1
beq x0, x1, L1

如上的指令表示,如果x0和x1的值相等,那么程序跳到L1标签处执行。

  • 有符号和无符号数的表示

一个二进制数,作为有符号理解的时候,只要把最高位的符号变成负号就可以,比如一个
8Byte的char,如果它做有符号计算,它的值是:

1
value = (-1)2^7 * bit[7] + 2^6 * bit[6] + ... + 2^0 * bit[0]

一个二进制数求相反数的快速方法是,对每个bit取反后整体加1。
一个扩展二进制负数的方法是把扩展位用1补齐。

  • 汇编到指令

计算机真正可以执行的是二进制指令,所以汇编代码要被编译器编译成二进制的指令。
二进制指令的格式设计会影响CPU的硬件设计,一个指令的格式被做成几种固定的样式。
比如,risc-v上的整数加法指令的二进制表示是:

1
2
3
4
5
6
7
8
9
10
add x9, x20, x21
+--------+-------+-------+---------+-------+---------+
|0000000 | 10101 | 10100 | 000 | 01001 | 0110011 |
+--------+-------+-------+---------+-------+---------+
| 7bits | 5 | 5 | 3 | 5 | 7 |
+--------+-------+-------+---------+-------+---------+
| funct7 | rs2 | rs1 | funct3 | rd | opcode |
+--------+-------+-------+---------+-------+---------+
| | x21 | x20 | | x9 | 51 |
+--------+-------+-------+---------+-------+---------+

可以看到一条加法指令的所有信息需要编码到一个32bit里,而且相关的算数指令都要复用
上面的样式。如上,用5个bit表示操作数寄存器,是因为risc-v上有32个构架寄存器,可见
如果要改变构架寄存器的数量,指令的编码也要跟着改。可以看到,对于立即数加的指令,
立即数也要编码到32bit里,这个立即数的大小是有限制的。

可见对于大一点的立即数加,需要有其他的指令,比如risc-v上有:

1
lui x19, immediate

加载立即数immediate到x19的31bit-12bit,可见其中的立即数最大可以是20bit。

  • risc-v过程控制分析

也就是函数调用过程的分析。一段函数代码执行的时候不仅要占用CPU的计算资源,也要
占用存储资源,比如算数指令要使用寄存器、临时变量可以存到寄存器里、寄存器不够的
时候临时变量就要存到栈上。除了临时变量要使用栈,使用寄存器的时候也要提前把寄存器
中的旧值保存到栈上,这样当函数执行完,才能恢复原来寄存器里的值,调用点之后继续
执行的逻辑才是对的。

risc-v中的返回地址保存在x1中,传参放在x10-x17八个寄存器,栈指针是x2,x8是帧指针。
risc-v的过程调用的ABI中包括:x5-x7, x28-x31是临时寄存器,调用过程中可以不被被调用者
保存,x8-x9, x18-x27,调用过程中必须被保存。

第三章

本章具体讲加减乘除以及浮点指令的实现。(跳过)

第四章

这一章是整本书的核心,可以先看这一章,有什么不懂再返回去看。这章循序渐进的把上面
提到的三种指令都加到一个简单的CPU实现中,然后引出流水线的设计,基于流水线再次实现
上面的三种指令,然后引出流水线里存在的数据冒险,控制冒险和结构冒险,然后引出相关
的解决办法,数据冒险的解决办法是前递,就是数据提交前就回传给后面的指令使用,控制
冒险的解决办法是各种分支预测。这章的最后引出了多发射的概念,然后得出超标量处理器
的概念。

从上面的指令分析里可以看出一个处理器需要的逻辑部件有哪些。算数指令、存储指令和
流程控制指令都需要运算单元,存储指令要用基地址和偏移量来计算地址,流程控制指令
要计算下一个PC的值,运算单元我们叫ALU。CPU里需要有取指令和译码的部件,这个部件
根据pc值从存储器里取指令并且做指令译码。CPU里需要有数据存取的部件,ld、sd需要用
这个部件访问数据存储器。实现上,CPU里还有寄存器堆(register fils),这个部件提供
其他部件对系统寄存器的读写接口,关于寄存器,CPU上一般有架构寄存器和物理寄存器,
架构寄存器就是软件可以看到的那些寄存器,物理寄存器是CPU内部扩展的寄存器,软件
不可见,一般CPU内部要对汇编代码里的架构寄存器做rename,把他们重定义成物理寄存器,
这本书,关于rename的东西也基本没有介绍。CPU里需要有相关的控制逻辑,把如上的各个
部件协调控制起来。

一个指令的执行可以被拆分成几个独立的步骤,每个步骤在流水线的一步里完成。一个指令
流顺序进入流水线,可以做到指令并行。这几个步骤一般是:取指,译码,执行,访存和写回。
乱序执行的处理器中,相关的步骤有所不同。

1
2
3
+----+    +----+    +----+    +-----+    +----+
| IF |--->| ID |--->| EX |--->| MEM |--->| WB |
+----+ +----+ +----+ +-----+ +----+

如下的连续指令会导致流水线数据冒险:

1
2
add x19, x0, x1
sub x2, x19, x3

原因是add的结果会存到x19里,这个在流水线的WB阶段才可以生效,而sub指令的EX步骤要
依赖x19。当然,编译器可以做到不要产生这样的依赖。数据冒险会带来流水线停顿,直观
的看,sub的指令可以在EX停顿,等到x19的值得到再继续。硬件上用前递的方式减轻数据冒险
带来的流水线停顿,前递就是增加额外的路径,把x19的值一算出来就给sub指令,可以想到
前递需要增加相关的数据冒险检测逻辑和前递数据通路的逻辑。

如下的连续指令会导致流水线控制冒险:

1
2
3
add x4, x5, x6
beq x1, x0, 40
or x7, x8, x9

简单讲,流水线需要指令紧密进入执行,停顿就会性能下降。而像流程控制这样的指令会
导致pc指针跳变,要等到x1、x0的计算结果得到后才知道有没有选错送入流水线的指令分支,
如果选错了,之前执行的结果都不能要了,要重新执行。

控制冒险是一定会存在的。缓解的办法就是各种分支预测手段,如果分支预测的基本准确,
性能影响就不大。

流水线的实现会在级与级之间增加多余的寄存器,保证各种控制逻辑和中间存储的实现。

1
2
3
4
5
       |         |         |          |
+----+ | +----+ | +----+ | +-----+ | +----+
| IF |-+->| ID |-+->| EX |-+->| MEM |-+->| WB |
+----+ | +----+ | +----+ | +-----+ | +----+
| | | |

处理器的异常设计和流水线是结合在一起的,因为指令的执行随时可以产生异常。

如上的内容都是流水线相关的,叫指令级并行(ILP)。曾加并行度的另外的方式还有多发射、
多核。多发射是指每个周期可以发出多条指令,直观的看,在一个核里并行的多几条流水
线可以支持多发射,多发射可以分为静态多发射和动态多发射。

动态多发射处理器也称为超标量处理器。现在的处理器,一般4发射做的比较多。

第五章

本章讲和存储相关的东西,具体是cache、虚拟内存管理、页表、TLB相关的东西。内容基本
是现在已知的东西,讲cache一致性的部分没有展开讲store buffer和invalid queue的东西,
barrier的东西都没有涉及。(跳过)

第六章

本章讲向量指令以及多核的东西,简单介绍了GPU的结构。 (跳过)