处理器体系结构
目录
处理器体系结构
我们看到的计算机系统都只限于机器语言程序级。处理器执行一系列指令每天指令执行某个简单操作,它们被编码为由一个或多个字节序列组成的二进制格式。一个处理器支持的指令和指令的字节集编码成为它的指令集体系结构(ISA)。
在本章的学习中,我们的学习目标如下:
- 了解ISA抽象的作用
- 掌握ISA,并能举一反三学习其他体系结构
- 了解流水线和实现方式
各章节需要掌握的重点内容如下:
- 了解Y86-64指令集体系结构,掌握汇编指令与机器码之间的转换,学会将C代码、X86汇编代码翻译为Y86-64代码;
- 了解逻辑设计和硬件控制语言HCL,掌握组合电路和HCL布尔表达式;
- 了解SEQ顺序处理器执行的6个阶段,掌握Y86-64所有指令个阶段的处理情况;
- 了解SEQ硬件结构,掌握分析硬件结构图的方法,以及某个寄存器的HCL描述;
- 了解流水线的通用原理,处理冲突和冒险的方法。
Y86-64指令集体系结构
程序员可见状态
这里的程序员既可以是用汇编代码写程序的人,也可以是产生机器级代码的编译器(详见第一章图1.3编译系统)。
编译器就是将“一种语言(通常为高级语言)”翻译为“另一种语言(通常为低级语言)”的程序。一个现代编译器的主要工作流程:源代码 (source code) → 预处理器 (preprocessor) → 编译器 (compiler) → 目标代码 (object code) → 链接器 (Linker) → 可执行程序 (executables)
程序员可见状态为15程序寄存器(RF)、3个条件码(CC)、程序状态(Stat)、程序计数器(PC)和内存(DMEM),如下图所示:
(图2.2.1)
- 程序寄存器:每个程序寄存器存储一个64位(即8字节)的字,这与Y86-64指令集的指令长度相匹配,它只包括8字节整数操作,称之为“字”不会产生歧义。
- 条件码:包括ZF、SF、OF3个一位的条件码,保存着最近的算数或逻辑指令所造成影响的有关信息。
- 状态码:表明程序执行的总体状态,一共包含 AOK、HLT、ADR、INS四种状态码,代码的值和含义如下表所示:
(2.1.2)
- 程序计数器:存放当前正在执行指令的地址
- 内存:是个很大的字节数组,存放程序和数据。
Y86-64指令
想要了解Y86-64指令集及其对应的编码,只要看懂下面这三个图就够了:
第一张图是Y86-64指令编码示意图,其中黄色框内的三个指令并非真实的Y86-64指令,OPq代表4个整数操作指令、jXX代表7个跳转指令、comvXX代表6个条件传送指令,其它指令如图所示:
(2.1.3)
如图所示,Y86-64指令集编码长度从1字节到10字节不等,这里我们将每字节(8位)用两个十六进制数(4位)表示,指令的详细解释如下:
1.halt指令用于停止指令的执行,执行halt会导致处理器停止,并将状态码设置为HLT。该指令占1个字节,用十六进制表示为「00」;
2.nop指令为占位指令,其作用参考NOP指令作用,这里我们不展开描述。该指令占1个字节,用十六进制表示为「10」;
3.rrmovq rA,rB指令作用是将rA寄存器中的值放入rB寄存器中。该指令占2个字节,用十六进制表示为「20 rArB」,其中“rA”、“rB”为两个代表寄存器的4比特数,要根据实际指令使用哪个寄存器来决定(其他涉及寄存器的传送指令同理),寄存器与数字编码的对应关系如下图所示:
(2.1.4)
例如:指令“rrmovq %rbx,%rcx”对应的编码为:「20 31」
4.irmovq V,rB指令作用是将立即数V放入寄存器rB中。该指令占10个字节,用十六进制表示为「30 FrB V」,其中“V”为八字节小段方式存储的立即数,像irmovq、pushq、popq这些只用到一个寄存器的指令,会将另一个寄存器指示符设为0xF,表示没有寄存器;
『例如』:指令“irmovq $15,%rbx”对应的编码为:「30F30F00000000000000」。
『数字转换计算过程』:由于“15”的十六进制表示为“F”,所以要在其前面添加15个“0”凑成八字节的值“000000000000000F”,再将其写成小端方式为:“0F000000000000”
5.rmmovq rA,D(rB)指令的作用是将rA寄存器中的值放入以“rB+D”为地址的内存单元中。该指令占10个字节,用十六进制表示为「40 rArB D」,其中“D”为八字节小段方式存储的代表偏移量的数;
『例如』:指令“rmmovq %rcx,-3(%rbx)”对应的编码为:「4013FDFFFFFFFFFFFFFF」。
『数字转换计算过程』:由于“-3”十六进制表示为“83”,扩展为八字节为“8000000000000003”,计算机中负数是用补码表示的,所以将其装换为补码为“FFFFFFFFFFFFFFD”,再将其写为小端方式为:“FDFFFFFFFFFFFFFFFF”
6.mrmovq D(rB),rA指令的作用是将以“rB+D”为地址的内存单元中的数放入寄存器rA中。与rmmovq类似,该指令占10个字节,用十六进制表示为「50 rArB D」,其中“D”为八字节小段方式存储的代表偏移量的数;
7.OPq rA,rB为整数操作指令,其作用是将寄存器rA和寄存器rB中的值做整数运算,并把结果存入rB寄存器中。该指令占4个字节,用十六进制表示为「6fn rArB」,其中fn代表指令的“功能码”,由具体执行“addq、subq、andq、xorq”中的哪条指令决定fn的值,其对应关系如下图“Y86-64指令集的功能码”所示:
(2.1.5)
所有指令的前4位编码成为指令的“代码部分”,如OPq指令中的“6”,功能值只有在一组相关指令共用一个代码时才有用(除了OPq,还有前面所提的jXX和comvXX都属于共用相同代码的指令)
8.jXX Dest为跳转指令,其作用是跳转到以Dest为地址(准确的说是逻辑地址)的代码处,根据分支指令的类型(处理器根据fn确定)和条件码的设置来选择分支。该指令占9个字节,用十六进制表示为「7fn Dest」,与OPq类似,其中fn为功能码,由具体执行“jmp、jle、jl、je、jne、jge、jg”中的哪条指令决定fn的值,其对应关系如“Y86-64指令集的功能码”所示,Dest为用小端方式表示的8字节绝对寻址方式的地址值(也可以是某个语句标号);
『例如』:下述代码中指令“jmp loop”对应的编码为:「700C01000000000000」
.pos 0x100 #Start code at address 0x100
irmovq ……
rrmovq ……
loop:
rmovq ……
addq ……
jmp loop
『数字转换过程』:“.pos 0x100”的意思是目标代码起始地址为0x10C,即“irmovq ……”那行对应的地址为0x100,因为irmovq指令和rrmovq指令编码长度分别为10和2,所以“rrmovq ……”那行指令的地址为0x100+0xa=0x10a,“loop:”那行的地址为“0x10a+2=0x10c”,因此此处Dest的值为0x10c,用小端方式表示为“0C01000000000000”
9.cmovXX rA,rB为条件传送指令,其指令格式同rrmovq指令,不同点是只有当条件码满足需要的约束时才会更新目的寄存器的值。该指令占2个字节,用十六进制表示为「2fn rArB」;fn由具体执行“rrmovq cmovle cmovl cmove cmovne cmovge cmovg”中的哪条指令决定,其对应关系如“Y86-64指令集的功能码”所示;
10.call Dest指令的作用是将返回地址入栈,然后跳到Dest指向的目的地址。该指令占9个字节,用十六进制表示为「80 Dest」,其中Dest的作用类似于条件跳转指令;
11.ret指令的作用是从call指令的调用中返回。该指令占1个字节,用十六进制表示为「90」;
12.pushq rA为入栈指令,其作用是将rA寄存器中的值压入栈顶。该指令占2个字节,用十六进制表示为「A0 rAF」;
13.popq rA位出栈指令,其作用是将栈顶元素弹出到rA寄存器中。该指令占2个字节,用十六进制表示为「B0 rAF」。
应用部分
为了检验我们是否掌握了Y86-64指令与指令编码之间的转换方法,我们可以尝试着做做教材练习题4.1和4.2。这里将字节序列转换为Y86-64指令的方法总结如下:
- 通过代码部分确定指令长度,从而以指令为单位划分字节序列;
- 通过功能部分确定具体的指令;
- 通过寄存器指示符字节确定指令中涉及的寄存器;
- 通过转换数值部分以小段法编码的数字来确定立即数、偏移量、绝对地址等值。
进一步的,为了学会写Y86-64程序,我们可以从“改编x86-64汇编代码”入手,但是这里有几点需要注意(详细转换细节请见我的课下实践博客):
- Y86-64中要把movq指令转换为具体的rrmovq,rmmovq,mrmovq指令;
- Y86-64中OPq只对寄存器数据进行操作,可以借用%r8-%r14这些寄存器,先用“irmovq”指令将立即数放入寄存器中,再进行相关计算;
- Y86-64中没有加载有效地址指令leaq,需要用“addq”等指令来代替其功能;
- Y86-64中没有比较指令“cmpq”,可以用两个寄存器存放操作数的值然后用subq命令使两数相减来设置条件码;
- Y86-64中没有乘指令mulq,需要用addq来替换;
- “movq %fs:40, %rax”指令,需要弄清楚%fs:40的含义和类型;
- 由于Y86-64指令集中所以操作都以8个字节为单位,所以在转换“movl,addl”这些四字节指令时要额外注意,有时需要保护高四字节的值;
- cltq指令:用于把%eax符号扩展到%rax,所以要先判断%eax的符号位,再决定给高四个字节置1还是0;
- Y86-64指令集中没有leave指令,要用“rrmovq %rbp,%rsp”+“popq %rbp”来代替。
- 我们常常用异或一个数本身来代替直接将其赋值为0,可以防止字符串提前以“0”结束引发的数据丢失。
- 为了更加深刻的领会Y86-64指令的含义和用法,可以试着做做教材练习题4.3、4.4、4.5和4.6。
书中给出了一个叫做“YIS”的指令集模拟器的工具,它的目的是模拟Y86-64及其代码程序的执行,而不用试图去模拟任何具体处理器实现的行为,我们要学会使用它来调试程序、模拟在硬件运行商的结果。
参考老师的给出的实验楼资源,构建YIS步骤如下:
- cd ~/Code/shiyanlou_cs413(根据实际情况修改,选择自己的路径)
- wget http://labfile.oss.aliyuncs.com/courses/413/sim.tar
- tar -xvf sim.tar
- cd sim
- sudo apt-get install bison flex 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/libtcl8.6.so /usr/lib/libtcl.so
- make
测试YIS步骤如下:
- cd y86-code
- 进入测试代码,教材p239页代码为asuml.ys,可以通过make asuml.yo进行汇编,asuml.yo就是汇编后的结果,见教材p238。
- make all可以汇编运行所有代码结果
逻辑设计和硬件控制语言HCL
为实现一个数字系统需要三个主要的组成部分:计算对位进行操作的函数的组合逻辑、存储位的存储单元,以及控制存储器单元更新的时钟信号。
HCL(硬件控制语言)用来描述不同处理器设计的控制逻辑。
返回目录
教材课后习题总结
4.2题:考察“字节序列与Y86-64指令之间的转换”。此题关键要根据“代码部分”(即指令编码的前4位)确定其指令编码长度,从而将一个整体的编码划分为不同的指令。
『例如』:A.0x100:30f3fcffffffffffffff40630008000000000000字节序列转换为Y86-64指令为:
0x100: irmovq $-4,%rbx
0x10a: rmmovq %rsi,0x0800(%rbx)
『解析』:通过“30”我们得知第一个指令为irmovq ……
,编码长度为10字节,所以可以确定从“40”开始为下一个指令rmmovq ……
;再看寄存器指示符字节和数值部分,通过观察“Y86-64寄存器标识符”可以确定“30f3fcffffffffffffff”中的第二字节“f3”表示该指令至用到一个寄存器“%rbx”,后面八字节数字为以小端方式组织的立即数,将“fcffffffffffffff”转换为真值为-4。其他字节序列的转换过程类似,这里不再赘述。
4.3题:有了iaddq指令,我们可以省去P251页Y86-64代码中的第2-3行,直接用iaddq $8,%rdi
和iaddq $-1,%rsi
两条指令替换10-11行。
4.4题&4.5题:此题涉及到我们常用的几种操作:
xorq %rax,%rax
:用异或来给某个数置零andq %rsi,%rsi
: 自身相与来设置条件码,判断一个数是否为0- 用
pushq ……
和popq ……
来保护某个寄存器中的值 - 用
xorq %r11,%r11
和subq %r10,%r11
加上跳转语句jle……
来取一个数的绝对值:当前%r11中的值为%r10的值的相反数,如果执行subq %r10,%r11
后%r11小于等于0,则说明%r10中的值为正数,绝对值为其本身,如果%r11的值大于0,则说明%r10的值为负,用rrmovq %r11,%r10
给%r10取反。
此外,我们还经常用“x∧0”来给一个数置0,用“x∨1”来给一个数置1.