CS:APP 第四章学习笔记

CS:APP 第四章 Processor Architecture 的学习笔记

这章的主要内容为一个简化的指令集 Y86-64 的设计以及 Y86-64 处理器的实现顺序实现和 pipeline 实现

The Y86-64 Instruction Set Architecture

这部分定义了在这一章中用作演示的名为 Y86-64 的玩具 ISA

Y86-64 程序状态

  • 15 个寄存器x86-64 的寄存器除去 %r15为了简化编码
  • 3 个 status flag: ZFSFOF
  • program counter: PC
  • memory
  • status code: Stat用来表示程序正常运行或发生了异常

Y86-64 指令

Y86-64 指令大致上是 x86-64 的一个子集但在 operand 等方面有一些简化或区别

operand 与 x86-64 的区别是

  • ImmediateRegisterMemory 都只有 64 位的版本
  • Register 只有 15 个
  • Memory 不支持 (, ri, s) 的部分只能是 Imm/(rb)/Imm(rb)

condition code 只有六个即 signed compare: le/l/e/ne/ge/g

指令列表以及与 x86-64 的区别

  • irmovq/rrmovq/mrmovq/rmmovq即将 movq 按 operand 类型拆成了四个指令
  • addq/subq/andq/xorq它们只接受寄存器作为 operand且只设置 ZFSFOF 三个 status flag
  • jmp/jle/jl/je/jne/jge/jg包括 jmp 在内都只能跳转到固定的地址不接受寄存器作为 operand且这个地址是绝对地址而非相对于 PC 的地址
  • cmovle/cmovl/cmove/cmovne/cmovge/cmovg它们只接受寄存器作为 operand
  • call: 地址是绝对地址
  • retpushqpopqnop: 与 x86-64 基本相同
  • halt: 停止运行将 status code 设为 HLT

Y86-64 指令编码

Y86-64 通过对指令的简化同时也使编码得到了简化但相应地使得编码不紧凑会有浪费

CS:APP Figure 4.2 简明地展示了 Y86-64 的指令编码

Y86-64 指令编码示意图

指令类型的编码

指令编码的第一个 byte 表示指令的类型这个 byte 的高位叫做 code低位叫做 function其中 function 只在某几个指令有用特别地rrmovqcmovXX 的 code 是相同的这表示 rrmovq 可以看作一种特殊的 cmovXX

算术运算的 function: add 0, sub 1, and 2, xor 3

condition code 的 function: le 1, l 2, e 3, ne 4, ge 5, g 6jmp 的 function 为 0

Register Specifier Byte

除了 jXXcall指令编码的第二个 byte如果有的高低位分别表示一个 register identifier

register identifier 从 %rax0%r14EF 表示不是寄存器

Constant Word

irmovqrmmovq/mrmovqjXX/call分别有一个 8-byte 的 constant word用来表示 immediate value 或地址byte ordering 是 little endian

Y86-64 异常

status code Stat 有四种可能的取值

  • AOK: 正常
  • HLT: 执行了 halt 指令
  • ADR: 访问了不合法的地址
  • INS: 指令编码不合法

在 Y86-64 中遇到异常后处理器会立即停止运行

Y86-64 程序

CS:APP Figure 4.8 展示了一个完整的 Y86-64 程序

完整 Y86-64 程序的汇编与机器码

可以下载 Y86-64 tools 并使用 yas 进行汇编使用 yis 模拟运行编译 yas需要添加 -fcommon 编译选项

对 %rsp 进行 push/pop

pushq %rsppopq %rsp 这两条指令虽然没什么用但它们的行为可能有歧义所以在设计 ISA 时明确规定它们的行为是有必要的

Y86-64 遵循和 x86-64 相同的规则pushq %rsp 会将旧的没有减 8 的%rsp 的值入栈popq %rsp 相当于 mrmovq (%rsp), %rsp

Logic Design and the Hardware Control Language HCL

这一章中使用玩具语言 HCL (hardware control language) 来描述 Y86-64 处理器的逻辑设计与之类似但不是玩具的语言例如 VHDLVerilog 等叫做 hardware description language (HDL)

逻辑门

CSAPP Figure 4.9:

与或非逻辑门

  • 图中只展示了输入个数为 2 的 AND 和 OR但可以有更多输入
  • 一旦输入改变逻辑门的输出很快就会随之改变

组合逻辑电路

组合逻辑电路即由若干逻辑门组合而成的电路它的特点是无状态输出仅与输入有关输入改变后输出很快就会随之改变

在 HCL 中用逻辑表达式来表示组合逻辑电路例如 bool eq = (a && b) || (!a && !b) 表示计算 ab 是否相等的电路因为它表示的是电路而不是计算在这条语句之后一旦 ab 的值发生改变eq 的输出也会改变和 Vue 的 computed 类似

以 word 为单位进行操作的电路

在处理器的设计中经常需要对一个 word 而非单个 bit 进行操作

在 HCL 中一般使用大写的名称表示 word例如: bool Eq = (A == B) 表示计算 word AB 是否相等的电路可以实现为判断每个 bit 是否相等再 AND

Multiplexor (MUX)

multiplexor (MUX) 的功能是通过信号输入的值来在其它输入中选择一个作为输出word-level 的 MUX 电路如图 (CSAPP Figure 4.13)

word-level MUX 电路

在 HCL 中使用 case expressions 表示 MUX例如

word Mux = [
    !s1 && !s0: A;
    !s1: B;
    !s0: C;
    1: D;
];

表示一个由 s0s1 控制的ABCD 中选一个作为输出的 MUX

case expression 在逻辑上的语义是依次判断每个条件以第一个满足的条件作为输出类似于 Rust 的 match

下面的 HCL 代码表示计算 ABC 中的最小值

word Min3 = [
    A <= B && A <= C: A;
    B <= C: B;
    1: C;
];

Arithmetic/logic unit (ALU)

ALU 是用来进行算术/逻辑运算的组合逻辑电路元件它接收两个 data input 以及一个表示进行何种运算的 control input输出运算的结果

测试值是否属于集合

在 HCL 中可以使用 in 来表示测试值是否属于集合的电路例如:

bool s1 = code in { 2, 3 };
bool s0 = code in { 1, 3 };

Memory and Clocking

组合逻辑电路是无状态且实时更新的与之相对memory 可以存储状态但更新由 clock 控制

这一章中会用到的 memory 有两大种三小种

  • clocked register: 存储一个值有一个输入和一个输出输出即存储的值而每次 clock rise 时会将存储的值修改为输入
  • random access memory:
    • register file: 存储 15 个值在 Y86-64 处理器中有两个 read port 和一个 write port
      • 每个 read port 有一个输入 src 表示 register identifier有一个输出 val 表示这个 register 存储的值src 改变后 val 会立刻改变
      • write port 有一个输入 dst 表示 register identifier另有一个输入 val 用于写入每次 clock rise 时如果 dst 不是 F 就会将 val 写入相应的 register
    • data memory: 存储很多个值用地址进行索引
      • 有一个地址输入 address
      • 有一个信号输入 write 表示进行写入而非读取
      • 有一个数据输出 data outwrite 为 0data out 会立刻输出 address 处存储的值
      • 有一个数据输入 data inwrite 为 1在 clock rise 时会将 data in 写入 address
      • 有一个信号输出 erroraddress 不是合法地址时输出 1

可以看到这几种 memory 的共同点是读取是实时的但写入由 clock 控制

在 Y86-64 的程序状态中寄存器存在 register file 中status flagsprogram counterstatus code 存在 clocked register 中memory 存在 data memory 中

Y86-64 处理器还有一个额外的 read-only instruction memory 用来读取指令而在真实的处理器中这是和内存一体的

Sequential Y86-64 Implementations

这一节会实现一个名为 SEQ 的顺序执行的处理器在这个处理器中指令是按顺序一条接着一条执行的且每条指令都会在一个 clock cycle 内执行完毕这要求 clock cycle 很长会导致处理器的执行很慢下两节将对此进行优化

指令执行的阶段划分与具体操作

将指令的执行划分为多个阶段可以使行为有很大差别的不同指令有一定的统一性方便硬件实现

本节会将指令执行划分为六个阶段

  1. Fetch: 将指令编码中不同部分的值读取出来
  2. Decode: 读取寄存器的值我感觉 fetch 和 decode 这两个名字互换一下才比较对 🤔
  3. Execute: 执行运算
  4. Memory: 写入或读取内存
  5. Write back: 写入寄存器
  6. PC update: 更新 program counter

每个指令每阶段的具体操作如图CS:APP Figure 4.18~4.21Solution 4.17

OPq, rrmovq, irmovq

rmmovq, mrmovq

pushq, popq

jXX, call, ret

cmovXX

SEQ 的主体电路

CS:APP Figure 4.23 大致展示了 SEQ 的主体电路

SEQ 主体电路

其中蓝色的元件是 black box灰色的元件会在后面进行设计还有部分电路连接没有画出来

这个电路大概看着有个印象即可细节会在后面说明

SEQ 的时序控制

在 SEQ 中每个时钟周期执行一条指令而时钟控制的只有各种 memory 的写入memory 的读取和运算都是用组合逻辑电路实现的虽然在逻辑上有执行顺序在电路上却是同时执行的可以看成一个关于 memory 的函数

也就是说整个执行过程是读取 memory 并计算出需要写入 memory 的值然后在 clock rise 时执行写入从而读取到新的 memory 的值而执行下一条指令

为了这个设计能够实现一条重要的原则是 No reading back即一条指令不能先更新再读取同一个值例如pushq不是先更新 R[%rsp] 再写入 M[R[%rsp]]而是先算出 valE再写入 M[valE]最后将 valE 写入 R[%rsp]又例如有的指令会修改 status flags有的会读取但没有指令既修改又读取

因为运算都是同时进行的执行的六个阶段实际上是六个部分

SEQ 的具体实现