(2022.12.26 Mon)
概念
指令集(instruction set)
直接被CPU执行的命令,a list of commands ready to be executed directly by CPU,用于
- tell CPU where to find data
- when to read the data
- what to do with the data
指令集的种类
- data transfer instruction
- arithmetic instruction
- logical instruction and bit manipulation
- processing control instruction
- program control instruction
- shift and rotate instruction
机器语言(machin language)
a collections of binary bits / digits that computer reads and interprets
计算机可以直接读和翻译的二进制数,由0和1组成。机器语言是电脑可以理解的唯一的语言。
汇编语言(assembly language)
low-level编程语言,用于电脑、微处理器、微控制器和其他可编程设备。
汇编语言比机器语言高一级。汇编有简单代码(simple codes)构成。汇编中的每个命令(statement)对应了一个被微处理器理解的机器码。将汇编程序变为机器码的软件成为汇编器(assembler)。
汇编语言、指令集和处理器架构的关系
汇编语言将机器码转化成人可以理解的语句,而汇编和机器码好比指令集的一体两面,汇编是指令集的符号编码(symbolic encoding),机器语言是指令集的二进制编码(bit encoding)。
比如MIPS指令集中的加法指令的汇编语言表示为
add $t0, $s1, $s2
其对应的二进制表达(机器码)为
000000 10001 10010 01000 00000 100000
这个二进制表达分为六个部分,含义如下:
-
000000
: op=0 (arithmetic) -
10001
: rs=17 ($s1
) -
10010
: rt=18 ($s2
) -
010001
: rd=8 ($t0
) -
000000
: shamt=0 (unused) -
1000000
: funct=32 (add
)
RISC和CISC
RISC, i.e., Reduced Instruction Set Computer精简指令集,是相较于CISC(Complex Instruction Set Computer复杂指令集)。不同的指令集下有不同的CPU架构,比如Intel的X86架构属于CISC,而ARM,MIPS,RISC-v都属于RISC。考虑到Huawei的hisi芯片采用的ARMv8-A架构,其也是精简指令集。
RISC是一种微处理器架构,其使用等长(uniform length)的简单指令(simple instructions)集,指令数通常不到100,这些简单指令通常在一个时钟周期(clock cycle)内执行。RISC芯片相对易于设计且价格低廉。缺点在于计算机要重复执行简单指令以完成大型程序(the setback is that the computer has to repeatedly perform simple operations to execute a larger program havng a large number fo processing operations)。
(2022.12.27 Tues)
CISC的处理器提供给用户变长(variable sizes)的数百指令。CISC架构包括完整的专门用途的电路,可用于高速执行这些指令。CISC中的指令通过复杂地址模型(complex addressing modes)与内存交互。CISC处理器减少程序尺寸,因此减少了运行程序的内存周期数,并提升了执行的总体速度。
对比RISC和CISC:
- 指令数:RISC指令简单且很少,通常不到100,CISC指令众多
- 执行时间:RISC的指令执行时间短,因其指令简单,且甚至没有乘法,需要通过加法实现;CISC有的指令执行时间过长,比如将寄存器(register)复制到内存中
- 长度:RISC固定长度,比如MIPS的长度为32位;CISC指令长度可变,比如IA32(?)从1到15位二进制表示
- 格式:RISC支持简单寻址格式(addressing format),只允许基址寻址(base addressing)和替代寻址(displacement addressing);MISC支持多种运算符,内存操作数指示器(memory operand specifier)可以有替代、基址和索引寄存器等多种组合。
- 队列(array):RISC不支持队列;CISC支持
- 算数和逻辑运算:RISC仅支持寄存器操作数,内存引用(memory referencing)只能通过加载和写入指令实现,也就是将内存写入寄存器和将寄存器写入内存;CISC中算数和逻辑运算都可应用于内存和寄存器操作数
- 条件代码(condition code):RISC不使用条件代码;CISC使用条件代码
- 过程变量和返回地址:RISC使用寄存器用于过程变量(procedure arguments)和返回地址,一些过程避免使用内存引用(memory references);CISC使用栈(stack)存储过程变量和返回地址
- 实现程序和机器程序:RISC中的实现程序(implementation programs)暴露于机器代码(machine-level programs),基本所有RISC机器都允许特定指令序列(specifi instruction sequences);CISC中程序与机器代码互相隔离/隐藏,指令集架构(ISA)提供了程序与执行之间的清晰抽象(clean abstraction)
(2022.12.27 Tues)
MIPS指令集
MIPS是32位RISC指令集,即每条指令占用32位二进制数且定长,存储于寄存器(register)中,32位因此是MIPS指令集的字长。
计算机硬件的操作数(operand)
和高级语言如C之类不同,指令集的寄存器数字受限,MIPS的寄存器累计32个,因大量的寄存器可能会使时钟周期变长。习惯上用0~31来代表寄存器,并加入美元符号$
用以引用寄存器,如$s0
,$t1
(临时寄存器)等。
一个简单的案例:
C语言中的赋值语句f = (g + h) - (i + j)
,其中的f, g, h, i, j分别赋予MIPS中的寄存器为$s0
,$s1
,$s2
,$s3
,$s4
,用MIPS指令集表达为
add $t0, $s1, $s2 # t0是g和h的和
add $t1, $s3, $s4 # t1是i和j的和
sub $s0, $t0, $t1 # s0是t0和t1的差
存储器操作数
前面的案例是对寄存器直接操作,但是寄存器数量少,大型程序中变量个数远超寄存器数字,C和Java中变量往往保存在存储器中。而MIPS中的算术运算操作又只对寄存器进行,于是需要指令在寄存器和存储器间传送数据。负责在寄存器和存储器间传送数据的MIPS指令称为数据传送指令(data transfer instruction)。
一个案例:
在C语言中计算g = h + A[8]
,g和h分别分配$s1
和$s2
,数组A长度为100,数组A的起始地址(或基址base address)保存在$s3
中,该寄存器称为基址寄存器(base register)。该命令经过编译则生成如下汇编指令
lw $t0, 8($s3)
add $s1, $s2, $t0
指令的第一条lw $t0, 8($s3)
,其中lw
即load word
,从基址寄存器并偏移8个内存地址的位置,即A[8]
处载入word到寄存器$t0
中。这里的数字8称为偏移量offset。
有符号数和无符号数
(2022.12.30 Fri)
一个数字是n进制数,有若干位。定义该数字的位数是从0开始的整数,比如二进制的000010
,只有第1位是1,其余位数如0、2、3、4、5都是0。
一个n进制数转换为十进制的公式为
其中的i
表示数字所在的位数,d
表示该位对应的数字,Base
表示的是n
进制如Base=2
,Base=16
等。
MIPS的最高、低有效位分别是最右、左的一位。
MIPS采用32位制。对于无符号数,数字范围是0到,最大数字也就是所有位数都是1的情况。
对于有符号数,即负数,计算机需要一种方法区分正负也就是符号。一种直觉方法是加入独立的符号位,这种表示方法称为符号和幅值(sign and magnitude)表示法。
但该表示法有两个弊端:
- 符号位所在位置并不明确,需要提前定义
- 不可能在计算时提前得知结果的符号,用sign and magnitude法需要额外的一步来设置符号
- 单独的符号意味着sign and magnitude方法表示的数中有正零和负零
因此sign and magnitude表示法很快被放弃。
另一个问题是,在试图用一个较小的数减大数时,无符号表示法将从前面的0中借位,所有结果中前面的位都变成一串1。
最终解决方案:易于硬件实现的方式,前导位为0表示正数,前导位为1表示负数。该方法称为二进制补码(two's complement)。
用补码的表示,计算机可直接从第一位确定该数的符号。补码的负数直接加1,则变为比它大1的补码数字,比如加1,则直接变为,也就是0,符合逻辑。
指令的表示
(2022.12.30 Fri)
指令在计算机内部采用高、低电信号来表示,在形式上和二进制数的表示相同。指令的各部分都可以看成是独立的数,将这些数拼在一起形成指令。
在几乎所有指令中都用到寄存器register,所以产生了一套规定,将寄存器名字映射成数字。在MIPS中寄存器$s0~$s7
映射到寄存器16~23,$t0~t7
映射到寄存器8~15。
在汇编语言和指令集的部分已经给出了一条汇编指令转换为机器码的案例。
add $t0, $s1, $s2
指令的数字形成称为机器语言(machine language),这样的指令序列称作机器码(machine code)。上面指令对应的二进制表达(机器码)为
000000 10001 10010 01000 00000 100000
这个二进制表达分为六个部分,
op | rs | rt | rd | shamt | funct |
---|---|---|---|---|---|
6 bits | 5 bits | 5 bits | 5 bits | 5 bits | 6 bits |
含义如下:
-
000000
: op=0 (arithmetic),指令的基本操作,称为操作码(opcode),表示操作和格式 -
10001
: rs=17 ($s1
),第一个源操作数寄存器 -
10010
: rt=18 ($s2
),第二个源操作数寄存器 -
010001
: rd=8 ($t0
),用于存放操作结果的目的寄存器 -
000000
: shamt=0 (unused),位移量(shift amount) -
1000000
: funct=32 (add
),功能,称为功能码(function code),用于指明op字段中操作的特定便是。
指令的布局形式称为指令格式(instruction format)。MIPS指令占32位3,与数据字的位数相等。遵循简单源于规整的原则,所有MIPS指令都是32位长。
为便于阅读,2进制数有时会表示成16进制数,16位的2进制只需要4位16进制数即可表达。
重新审视上面的指令。当指令需要比上述字段更长的字段时,问题发生。如,取字指令必须指定两个寄存数和一个常数。在上述格式,如果地址使用其中的一个5位字段,取字指令的常数就被限制在之内,也就是32位之内。而常数通常到数组或数据结构中选择元素,比32大得多,显然5位字段用处不大。
即希望所有指令长度相同,又希望具有统一的指令格式,就产生了冲突。MIPS设计者给出一个这种方案:保持所有的指令长度相同,但不同类型的指令采用不同的指令格式。
不同类型的指令采用不同的指令格式。上面案例的格式称为R型(用于寄存器)。另一种格式称为I型(用于立即数),立即数(immediate number)和数据传送指令用的就是这种格式。I型的字段如下
op | rs | rt | constant or address |
---|---|---|---|
6 bits | 5 bits | 5 bits | 16 bits |
16位的地址字段意味着取字指令可以取相对于基址寄存器地址偏移
(2023.01.02 Mon)
R型和I型指令,前16位相同,包含1) 给出基本操作的
op
字段,2) 给出第一源操作数的rs
字段,3) 给出第二源操作数的rt
字段(取字指令除外,在取字指令中用于指定目的寄存器)。R型最后16位划分为3个字段:1)rd
字段中的目的寄存器,2)shamt
字段,3)funct
字段的R型指令的特定辅助操作。I型最后16位合并为一个address
字段。
当代计算机基于如下两个原则构建:
- 指令以数字为表达形式
- 与数据相同,程序存储在存储器中,可读写
这两个原则引出存储程序(stored-program)概念。存储器可存放编辑器程序的源代码、编译后的机器码、编译后的程序需要使用的文本,甚至生成机器码的编译器。
指令表达为数的好处是程序当成二进制数的文件发行,计算机可以沿用那些指令集兼容的现成软件。
MIPS决策指令 if-else等
(2023.01.02 Mon)
程序语言中决策指令,由if
语句描述,也使用go to
语句加标签实现。MIPS中有两条类似于if
和go to
语句功能的指令,分别是
beq register_1, register_2, L1
如果register_1
和register_2
中的数值相等,则转到标签为L1
的语句执行,beq
代表相等则分支(branch if equal).bne register_1, register_2, L1
如果register_1
和register_2
的数值不等,则转到标签为L1
的语句执行,bne
代表如果不相等则分支(branch if not equal)
这两条指令传统上称为条件分支(conditional branch)指令。
案例:执行高级语言中的命令if (i == j) f = g + h; else f = g - h;
,需要一条beq
或bne
指令
bne $s3, $s3, Else # go to Else if i != j
执行单一操作,所有操作数给寄存器,则只要一条指令
add $s0, $s1, $s2 # f = g + h (skipped if i != j)
在if
命令的结尾部分,需要引入另一个分支指令,通常叫无条件分支指令(unconditional branch)。遇到这种指令,程序必须分支。为区分条件分支和无条件分支,MIPS将无条件分支指令命名为jump
,简写为j
j Exit # go to Exit
if
语句中的else
部分的赋值语句可编译为一条指令。只需要将标签Else
加在这条指令前、标签Exit
加在该条指令后面,表示if-then-else
编译代码结束:
Else: sub $s0, $s1, $s2 # f = g - h (skipped if i = j)
Exit:
循环
案例:C语言编写的传统循环程序:
while (save[i] == k)
i += 1;
设定i
和k
存放在寄存器$s3
和$s5
中,数组save
的基址存放在寄存器$s6
中。
placeholder
case/switch
语句
最简单的实现方法是借助一系列的条件判断,将switch
语句转化为if-then-else
语句嵌套。
另一种有效的方法是将多个指令序列分支的地址编码为一张表,即转移地址表(jump address table)或转移表(jump table),程序只需要索引该表即可跳转到恰当的指令序列。转移表是一个由代码中标签所对应地址构成的数组。程序需要跳转的时候首先将转移表中恰当的项加载到寄存器中,使用register中的地址值进行跳转。MIPS提供了寄存器跳转指令jr(jump register)
,用来无条件的跳转到寄存器指定的地址。
MIPS过程procedure和硬件对过程的支持
placeholder
MIPS立即数和寻址
placeholder
MIPS同步
placeholder
C语言代码转换为汇编语言的过程
C语言代码转换为汇编代码的实例
Reference
1 计算机组成与设计(硬件软件接口),D. Pattersson, J. Hennessy