本笔记只是以下课程的讲义【翻译版】!持续更新中。。
原课程:National University of Singapore, IT5002 Computer Systems and Apllication, Prof. Colin Tan
可视化网站: 指令操作可视化网站
Number System
Data RepresentationC语言里基本的数据类型:int, float, double, char
(变体:short, long)
数据是由一串比特数组成的(0/1)。
同一串二进制数,数据类型不同,代表着不同含义:
01000110:作为int,他是70;作为char,他是’F’
单位
- 比特
bit:[b]inary dig[it]s- 字节
Byte:8 bits- 点
Nibble:4 bits(不怎么用)- 字
Word:多个bytes(可以是1 bytes, 2 bytes,4 bytes,etc. ),取决于所在计算机的架构方式
N bits可以表示最多
2
n
2^n
2n个数字 ,例如,2bits可以表示最多4个数字(00 01 10 11).
所以,如果需要表示M个数字,至少需要
⌈
l
o
g
2
M
⌉
\lceil log_2M \rceil
⌈log2M⌉个bits。 例如,32个数字需要5位比特来表示,1000个数字需要10位比特来表示
Decimal (base 10) 十进制数值系统是一个加权位置数值系统weighted-positional number system,他的基底是10(base / radix)。
符号Sysbols/digits:
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
0,1,2,3,4,5,6,7,8,9
每个位置上都有对应的权重:
w
e
i
g
h
t
=
1
0
n
weight = 10^{n}
weight=10n
例如:
(
7594.36
)
1
0
=
(
7
×
1
0
3
)
+
(
5
×
1
0
2
)
+
(
9
×
1
0
1
)
+
(
4
×
1
0
0
)
+
(
3
×
1
0
−
1
)
+
(
6
×
1
0
−
2
)
(7594.36)_10 = (7 × 10^3) + (5 × 10^2) + (9 × 10^1) + (4 × 10^0) + (3 × 10^{-1}) + (6 × 10^{-2})
(7594.36)10=(7×103)+(5×102)+(9×101)+(4×100)+(3×10−1)+(6×10−2)
权重,10的k次幂,k =
n
,
n
−
1
,
.
.
.
,
0
,
小数点
−
1
,
−
2
,
.
.
.
,
−
m
n, n-1, ..., 0, 小数点 -1, -2, ..., -m
n,n−1,...,0,小数点−1,−2,...,−m

Other Number Systems在一些编程语言里,我们会使用特殊的标记来表明某个数字的基数:
8'b11110000表示一个八位二进制数111100008'hF0表示一个八位二进制数,其值为十六进制下的F08'd240表示一个八位二进制数,其值为十进制下的240Base-R to Decimal Conversion直接每一位乘以对应位置的权重
i
∗
R
n
i * R^n
i∗Rn

Decimal to Binary Conversion例如:
(1)整数
方法一:
要将整数转换为二进制,连续除以 2 直到商为 0。余数形成答案,第一个余数作为最低有效位 (LSB),最后一个余数作为最高有效位 (MSB) -》从下往上读余数。
43
43=(101011)B
43/2=21======余1
21/2=10======余1
10/2=5=======余0
5/2=2========余1
2/2=1========余0
1/2=0========余1
从下往上读余数
255
255=(11111111)B
255/2=127=====余1
127/2=63======余1
63/2=31=======余1
31/2=15=======余1
15/2=7========余1
7/2=3=========余1
3/2=1=========余1
1/2=0=========余1
方法二:
255 = 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1
= 第 7 6 5 4 3 2 1 0 位为1
= 11111111
(2)小数
要将小数转换为二进制,重复乘以 2,直到小数积为 0(或直到所需的小数位数)。 进位数字或进位产生答案,第一个进位作为 MSB,最后一个作为 LSB -》 从上往下读取出来的整数部分。
如:0.625=(0.101)B
0.625*2=1.25======取出整数部分1
0.25*2=0.5========取出整数部分0
0.5*2=1==========取出整数部分1
再如:0.7=(0.1 0110 0110…)B
0.7*2=1.4========取出整数部分1
0.4*2=0.8========取出整数部分0
0.8*2=1.6========取出整数部分1
0.6*2=1.2========取出整数部分1
0.2*2=0.4========取出整数部分0
0.4*2=0.8========取出整数部分0
0.8*2=1.6========取出整数部分1
0.6*2=1.2========取出整数部分1
0.2*2=0.4========取出整数部分0
Conversion Between Decimal and Other Bases类比1.5中十进制转换为二进制的方式:
我们可以推导到十进制转换为R进制的方式为:
Conversion Between Basesm进制转换为n进制的通用方法:先将m进制转换为十进制(每位乘以权重,求和),再将该十进制数转换为n进制(辗转相除/乘)

Binary to Octal/Hexadecimal Conversion2/8/16进制之间的相互转换有捷径:
二进制转八进制:每三位为一组换算
(10 111 011 001 . 101 110)_2 -> (2731.56)_8
2 7 3 1 . 5 6
八进制转二进制:每一位转成三位二进制数
(2731.56)_8 -> (10 111 011 001 . 101 110)_2
二进制转十六进制:每四位为一组换算
(101 1101 1001 . 1011 1000)_2 -> (5D9.B8)_16
5 D 9 B 8
十六进制转二进制:每一位转成四位二进制数
(5D9.B8)_16 -> (101 1101 1001 . 1011 1000)_2
ASCII 码和 Unicode 用于表示字符(‘a’、‘C’、‘?’、‘\0’等)
ASCII
American Standard Code for Information Interchange
所以整数0-127和字符在C语言里是可以互相转换的。
int num = 65;
char ch = 'F';
printf("num (in %%d) = %d\n", num);
printf("num (in %%c) = %c\n", num);
printf("\n");
printf("ch (in %%c) = %c\n", ch);
printf("ch (in %%d) = %d\n", ch);

int i, n = 2147483640;
for (i=1; i<=10; i++) {
n = n + 1;
}
printf("n = %d\n", n);
问以上代码的输出是什么?->-2147483646
因为C语言里int的范围是-2147483648~+2147483647
当超过2147483647时,再往上加,符号位会变为1,此时会变成负数,即-2147483648,继续加2,得到-2147483646
附:其他数据类型范围参考

Sign-and-Magnitude1s Complement2s ComplementSign and Magnitude符号由符号位上的数字表示,符号位上为1表示负数,0表示正数。
例如,1比特的符号+7比特的数值

00110100 -> (+110100)_2 = (+52)_10
10010011 -> (-10011)_2 = (-19)_10
因此,8比特组成的数字的范围: − 12 7 10 t o + 12 7 10 -127_{10} to +127_{10} −12710to+12710
对于 n 位sign-and-magnitude表示,可以表示的值的范围是多少? − ( 2 n − 1 − 1 ) 10 到 + ( 2 n − 1 ) 10 -(2^{n-1}-1)_{10} 到 +(2^{n-1})_{10} −(2n−1−1)10到+(2n−1)10
如果要把一个数字进行正负转换,只需要转换符号位。
例如:
0010 0001 (33) -> 1010 0001 (-33)
1000 0101 (-5) -> 0000 0101 (5)
1s Complement给定一个可以表示为 n 位二进制数的数字 x,它的否定值可以使用 1s 补码表示获得:
−
x
=
2
n
−
x
−
1
-x = 2^n - x - 1
−x=2n−x−1
例如,将8比特数 0000 1100(12)转换为-12:
−
00001100
=
2
8
−
12
−
1
=
243
=
11110011
(
1
′
s
)
−00001100=28−12−1=243=11110011(1′s)
−00001100=28−12−1=243=11110011(1′s)
使用第一类补数方式,对数值取反的方法:反转所有比特
最高位比特most significant bit(MSB)仍然表示符号(0为正,1为负)
(14)_10 = (0000 1110)_2 = (0000 1110)_1s
-(14)_10 = -(0000 1110)_2 = (1111 0001)_1s
-(80)_10 = -(0101 0000)_2 = (1010 1111)_1s
2s Complement给定一个可以表示为 n 位二进制数的数字 x,它的取反值可以使用 2s 补码表示获得:
−
x
=
2
n
−
x
-x = 2^n - x
−x=2n−x
例如,8-bit数 0000 1100(12)在第二类补数方式里是这样表示的:
−
00001100
=
2
8
−
12
=
244
=
11110100
(
2
′
s
)
−00001100=28−12=244=11110100(2′s)
−00001100=28−12=244=11110100(2′s)
使用第一类补数方式,对数值取反的方法:反转所有比特后+1
most significant bit(MSB)仍然表示符号(0为正,1为负)+(14)_10 = (0000 1110)_2 = (0000 1110)_2s
-(14)_10 = -(0000 1110)_2 = (1111 0010)_2s
-(80)_10 = -(0101 0000)_2 = (1011 0000)_2s
Comparisons
Complement on Fractions我们可以在小数上扩展补码的概念。
0101.01 -> 1010.10 (1s)
111000.101 -> 000111.010 (1s)
0101.01 -> 1010.11 (2s)
2s Complement Addition/Subtraction加法 A+B
减法 A-B = A + (-B)
有符号数的范围是固定的。
如果加/减的结果超出这个范围,就会发生溢出。
可以很容易地检测到溢出:正数加正数变成负数,负数加负数变成正数。
例如:在4-bit 2s-complement系统下,
0101 + 0110 = 1011 -> 5 + 6 = -5 ⇒ 溢出了
1001 + 1101 = 10110 (忽略首位进位) = 0110 -> -7 + -3 = 6 ⇒ 溢出了

1s Complement Addition/Subtraction加法 A+B
减法 A-B = A + (-B)

Excess Representation除了sign-and-magnitude和补码方案,超额表示是另一种方案。
它允许值的范围通过简单的转换(加法/减法)在正值和负值之间均匀分布。
示例: 3 位数字上的 Excess-4 表示。 见表。

对于 4 位数字,我们可以使用excess-7 或excess-8。 Excess-8 如下所示。

很多应用不仅包含整数计算,还包含实数的计算。
那么在计算机系统里如何表示实数呢?
因为比特的数量有限,实数通常会表示为一个大致的值。
Fixed-point Representation在定点表示方式中,给整数/小数部分的比特位数量是固定的。
例如,给定一个8-bit的表示,其中6bit是整数部分,2bit是小数部分。

如果我们使用2s complement,我们可以表示数字如下:
011010.11(2s) = 26.75(10)
111110.11 (2s) = -000001.01(2) = -1.25(10)
Floating-Point Representation定点表示方式有一个有限的范围。而浮点表示方式允许我们表示非常大/非常小的数字。例如:0.23 * 10^23
浮点表示方式中有3个组成部分:符号sign、指数exponent、小数mantissa(fraction)

基数为2。
包括两种形式:
Single-precision, 32bits:1-bit 符号位,8-bit 带127偏差的指数位(excess-127),23-bit的小数位。Double-precision, 64bits:1-bit符号位,11-bit 带1023偏差的指数位(excess-1023),52-bit的小数位。小数会被规范化为整数部分只有一个1,
例如:
110.1 -> normalised -> 1.101 * 2^2 -> 只有后面的101会被存在小数区域。
0.00101101 -> normalised -> 1.01101 * 2^-3 -> 只有后面的01101会被存在小数区域。
-6.5在IEEE 754 单精度浮点格式中是如何表示的?
-6.5 = -110.1 = -1.101 * 2^2

1 10000001 10100000000000000000000 = C0D00000作为不同的数据类型,表示的数也不一样:

维基百科定义:
MIPS(Microprocessor without Interlocked Pipeline Stages),是一种采取精简指令集(RISC)的指令集架构(ISA)。
Instruction Set Architecture, ISA1000110010100000 是一条让计算机将两个数字相加的指令。add A, B等同于1000110010100000syntactic sugar)Walkthrough
在计算机里的两大重要组成部分:处理器和内存 Processor and Memory

代码和数据保存在内存当中,在运行时装入处理器。

访问内存是很慢的,为了避免频繁地访问内存,在处理器中提供了一种用于暂时存储数据的结构,叫做寄存器 register

loadstore
Reg2Reg Arithmetic operation可以直接在寄存器上操作,这样速度可以更快。


repetition (loop)、选择Selection(if-else) 需要用指令来切换控制流control flow
condition fails,跳出该段程序。


Memory Instruction计算过程完成后,可以将寄存器的内容放回内存。

Summarystored-memory concept:指令和数据都存储在内存当中。load-store model:在执行过程中,减少对内存的操作,更多依赖于寄存器来暂时存储数据。Memory:将数据在内存和寄存器之间转移Calculation:算术运算等Control flow:改变执行的顺序Registers16-32个寄存器没有数据类型,不像程序变量一样,机器指令/编译指令假设存储在寄存器的数据是类型正确的。($0, $1, ..., $31)($a0, $t1)
$at (register 1) 是留给编译器用的。$k0 - $k1 (register 26-27) 是留给操作系统用的。#,任何在每一行#后面的都会被编译器忽略多数MIPS的算术/逻辑运算都有三个操作数:2个来源位置和1个目标位置。


我们假设a、b、c的数值已经分别装入了寄存器 $s0、$s1、$s2(变量映射 variable mapping)。(这里没展示内存操作指令。)
register-to-register。
$s1 / $s2的位置是很重要的,前减后。
Complex Expression
单个MIPS指令最多只能有两个源操作数,因此,需要将复合表达式拆分为多个MIPS指令。

中间数可以用暂存寄存器Temporary registers($t0, $t1)来存储。
示例:


Constant / Immediate Operands
Immediate value是数值常量,经常用于各类操作。MIPS专门为他们提供了一系列操作operations。addi指令:Add Immediate
add指令差不多,但是第二个源操作数是一个常量(而不是寄存器变量)16-bit 2s complement number system)subi指令,因为可以直接用addi加上一个负数。Why?因为芯片是很贵的,有资源限制,指令越少越好。$0 or $zero)$0 or $zero) 。
由于这种赋值操作非常常见,MIPS定义了一种伪指令 move。
⚠️注意:这只是为了表示起来比较方便,最后还是要转成上面那种指令的。

算术运算将寄存器的内容(32个比特)看作一个整体的量(有符号/无符号的整数);
逻辑运算将寄存器的内容看作分散的32个比特,这样就可以在一个字word里对每个bits/bytes做一些位运算

* 实际上not指令是不存在的,有些特殊的技巧可以达到取反的效果,后面说。
AND:全是1才是1OR:有1就是1NOR:全是0才是1XOR:exclusive or,异或,不一样才是1
Shiftingsll (shift left logical):将一个字word中的所有比特向左移动n位,后面补0。例如,将$s0左移4位。

srl (shift right logical) :将一个字word中的所有比特向右移动n位,前面补0。

Bitwise ANDand:按位与运算 Bitwise AND,对应位置上都是1,结果才是1,否则为0。and $t0, $t1, $t2
and 可以用作掩码操作 masking operation例如:我们只对寄存器$t1的最后12位感兴趣,将结果存在$t0,掩码mask应该是:

Bitwise ORor:按位或运算 Bitwise OR,两个字word对应位置其中一个为1结果位置就是1。or指令有一个常数版本ori指令。ori $t0, $t1, 0xFFF
Bitwise NOR
NOT指令(就是0变1,1变0),但提供了一个NOR指令(全是0,结果才是1)。NOT的效果可以通过nor $t0, $t0, $zero得到。(当某位上是0时,和0 nor,两个都是0,结果为1;当某位上是1时,和0 nor,有一个不是0,结果为0;由此达到0变1,1变0的效果)NOT指令?设计原则:让指令集尽可能小。Bitwise XOR
XOR得到NOT的等效结果? (a XOR 1 -> NOT a)$t2的比特位上全为1,执行xor $t0, $t0, $t2。(XOR:不同才是1,当某位上是0时,和1不同,得到1;当某位上是1时,与1相同,得到0)NORI,却有XORI?【待补充】Large Constant维基百科:CPU 字长的定义就是通用寄存器的宽度,64位CPU是指CPU内部的通用寄存器的宽度为64位元
怎么将一个32位的常数放进寄存器里?如 10101010 10101010 11110000 11110000
Note: 操作常量的范围是
[
−
2
15
,
2
15
−
1
]
[-2^{15}, 2^{15}-1]
[−215,215−1] (使用16-bit 第二类补数系统 16-bit 2s complement number system)
lui (load upper immediate)指令设置 上16位:lui $t0, 0xAAAA #10101010 10101010
ori (or immediate) 设置低位比特:ori $t0, $t0, 0xF0F0 #11110000 11110000

其中,
Memory Organisation (General)address,就是数组的索引。
如图,每个存储单元包含了一个字节Byte(8个比特bits)。【字节寻址方式】

Transfer UnitByte addressable的机器)Word addressable方式的机器)字 WORD:
寄存器的大小 / 整数的大小 / 指令大小 是一样的维基百科
word: 对于某种特定的计算机设计而言,字(英语:word)是用于表示其自然的数据单位的术语。在这个特定电脑中,字是其用来一次性处理事务的一个固定长度的位(bit)组。一个字的位数(即字长)是电脑系统结构中的一个重要特性。
字长度在计算机结构和操作的多个方面均有体现。电脑中大多数寄存器的大小是一个字长。电脑处理的典型数值也可能是以字长为单位。CPU和内存之间的数据传送单位也通常是一个字长。还有在内存中用于指明一个存储位置的地址也经常是以字长为单位的。
Word Alignment如果word的起始字节地址 是word中字节数的倍数,则word在内存中对齐。(所以找下一个字就地址+4)
例子:如果一个字包含4个字节:

我们怎么快速判断一个给定的内存地址是不是word-aligned的?
看看起始位置能不能被word中字节数整除。
Memory Instructionsload-store寄存器架构。

Load Word例:lw $t0, 4($s0)
步骤:
$s0 + 4 = 8000 + 4 = 8004Mem[8004], 8004-8007)读取到$t0
store word例:sw $t0, 12($s0)
步骤:
$s0 + 12 = 8000 + 12 = 8012$t0的内容存储到 起始地址为8012的一个word里(Mem[8012], 8012-8015)
load and store instructions只有load和store指令可以访问内存中的数据。
例:每个数组元素占据一个word(4 bytes)。
$s3 包含了数组A的起始地址(A[0]的地址)h被映射为$s2
除了 lw 和 sw外,还有:
lb: load byte,读取字节sb: store byte,存储字节他们的格式都差不多(这里偏移量offset也不用乘以4了):
lb $t1, 12($s3)sb $t2, 13($s3)注意:
MIPS不允许用lw/sw存取非对齐字 unaligned word,如果一定要存取非对齐字,可以使用伪指令ulw/usw (unaligned load word / unaligned store word)。
lh / sh :存取半个字 load/store halfwordlwl / lwr / swl / swr:存取在左右边的字 load/store word left/right Array
⚠️ 注意:寄存器没有数据类型!
例如:
add $t2, $t1, $t0: $t1, $t0 应该是数值lw $t2, 0($t0):$t0 则应该是地址⚠️ 注意:在以字节寻址的机器,连续的字地址序列不是相差1的。
通常会犯的错误:认为下一个字的地址可以通过寄存器中存放的地址 + 1得到,实际上应该是寄存器中存放的地址 + 字长
对于lw和sw:基础地址 + 偏移量 base address + offset必须是4的倍数。
Swapping Elements
(注:这是个简化版本,不是完整的指令)
Make decisionsif / goto语句control flowbne $t0, $t1, labelbeq $t0, $t1, labelj labellabel是编译语言中的“锚点anchor”,用于标识目标位置,label不是 一种指令。Conditional Branch: beq and bne当某个条件满足时,处理器才会进入某一分支。
beq $r1, $r2, L1:当r1 r2相等时,跳转到label为L1的指令 (if (a == b) goto L1)
beq: brach if equal
bne $r1, $r2, L1:当r1 r2不相等时,跳转到label为L1的指令 (if (a != b) goto L1)
bne: brach if not equal
Jump: j处理器始终会进入该分支。
j L1:无条件进入label为L1的指令 (goto L1)beq $s0, $s0, L1IF statement
这两种等效的方式,右边效率更高。
⇒ 因此引申出一个通常的技巧:将条件倒过来,用更短的代码执行。

用beq重写
beq $s3, $s4, Else
sub $s0, $s1, $s2
j Exit
Else: add $s0, $s1, $s2
Exit:

问以上编译代码的原始高级语言语句是什么?
if(i == j) {
f = 0;
}
Loops
任何形式的循环都能在编译语言里用条件分支和跳转语句表示出来。

问:对应的MIPS代码是什么?
Loop: bne $s4, $s5, Exit
add $s3, $s3, 1
j Loop
Exit:

写出以上循环语句的MIPS指令
add $s0, $zero, $zero
addi $s1, $zero, 10
Loop: beq $s0, $s1, Exit
addi $s2, $s2, 5
addi $s0, $s0, 1
j Loop
Exit:
我们有了beq和bne,有没有“如果小于,进入分支”(brach-if-less-than,blt)呢?
实际上是没有blt的,需要使用slt(set on less than)或者slti来构造。

构造一个blt $s1, $s2, L的指令:

blt也是一个伪指令,编译器会把它转化为两行等式代码。
Array and Loop
$t0,结果存储在$t8// 简单的C语言代码
result = 0;
i = 0;
while (i < 40) {
if (A[i] == 0)
result++;
i++;
}
Address of A[] -> $t0
Result -> $t8
i -> $t1
编译 方法一:
addi $t8, $zero, 0 # 初始化结果变量
addi $t1, $zero, 0 # 初始化数组指针
addi $t2, $zero, 40 # 确定数组终止位置
Loop: bge $t1, $t2, end # 如果当前数组指针大于数组终止位置,跳出循环
sll $t3, $t1, 2 # i * 4 (指针向下移动四位)
add $t4, $t0, $t3 # &A[i] 计算基地址加上偏移量后目标值的位置
lw $t5, 0($t4) # $t3 <- A[i] 读取地址指针在t4位置上数组的值,赋给t3
bne $t5, $zero, skip # 如果A[i] != 0,跳过
addi $t8, $t8, 1 # 如果A[i] == 0,结果++
skip: addi $t1, $t1, 1 # i++
j loop # 下一轮循环
end:
Address of A[] -> $t0
Result -> $t8
i -> $t1
编译 方法二:用指针
addi $s8, $zero, 0 # 初始化结果变量
addi $t1, $t0, 0 # i + 基地址:当前元素的地址
addi $t2, $t0, 160 # $t2 <- 基地址 + 160 (&A[40])
loop: bge $t1, $t2, end # 如果当前数组指针大于数组终止位置,跳出循环
lw $t3, 0($t1) # $t3 <- A[i] 将A[i]数值读取至t3
bne $t3, $zero, skip # if(A[i] != 0) 跳过
addi $t8, $t8, 1 # result++
skip: addi $t1, $t1, 4 # 转移到下一个元素(A[i]地址+4 -> A[i+1]的地址)
j loop
end:

翻译一下(不是规范的C代码):
t1 = 10;
t1 = t1 + t1 = 20;
t2 = 0 + 10 = 10;
do{
t2 = t2 + 10; // 10 + 10 = 20
t1 = t1 - 1;
} while(t1 == 0); // 此时t1==9,不用下一轮
1. 有多少指令被执行了? 6行。
2. $t2的最终结果是多少? 20。

翻译一下(不是规范的C代码):
t0 = 0 + 0 = 0;
t1 = t0 + t0 = 0 + 0 = 0;
t2 = t1 + 4 = 0 + 4 = 4;
do{
t1 = t1 + t0; // t1 = 0 -> 0 + 1 -> 1 + 2 -> 3 + 3 -> 6 + 4
t0++; // t0 = 1 -> 2 -> 3 -> 4(这三行走多少遍就看t0什么时候加到t2(4) -》走4次结束)
} while(t2 != t0) // 4 != 1 ->
1. 有多少指令被执行了? 3 + 4 * 3 = 15
2. $t1的最终结果是多少? 10

翻译一下(不是规范的C代码):
t0:word数组的初始地址
t1 = t0 + 10 // 初始地址下移10个字节
t2 = 0
do {
t3 = A[]初始地址下移10个字节的地址里存的值
t2 += t3 // t2加上上面这个值
t1--; // 地址指针上移一位
} while(t1 != t0) // 当地址指针到数组头地址前,继续循环
1. bne指令会执行多少次? 从9到0,10次。
2. 有多少次bne跳进了 label loop分支? 9次。10次中除了最后一次相等了,其他都会跳回去。
3. 有多少指令被执行了? 2 + 4 * 10 = 42
4. 有多少单一的字节被读取了read from memory? 13。每次读4个,读到t0的时候会读多3个其他的。

MIPS EncodingMIPS Instruction Classification指令通过操作数区分:



这类指令的二进制格式为(数值表示当前区块被分配了多少个二进制位):

每一块都是一个独立的5或6比特的无符号整数 。


先用十进制表示

转换成二进制表示

把他们按四位一划分,再用16进制表示


先用十进制表示

转换成二进制表示

把他们按四位一划分,再用16进制表示





shamt只能表示0-31
这类指令的二进制格式为(数值表示当前区块被分配了多少个二进制位):

opcode、rs、rt和在r-format的位置是一样的。
opcode:由于没有funct的位置,这里的opcode单独确定一个指令。rs:确定源寄存器rt:确定接收结果的寄存器(r-format里面是用rd接收)immediate:
有符号的整数,16bits ->表示最多
2
1
6
2^16
216个不同的数字。lw / sw中的偏移量addi / subi / slti指令中的数值

先用十进制表示

转换成二进制表示

把他们按四位一划分,再用16进制表示


先用十进制表示

转换成二进制表示

把他们按四位一划分,再用16进制表示

beq / bne / j)程序计数器 Program Counter (PC):一个用于记录在处理器中执行的指令地址的特殊寄存器。
PC-Relative Addressing
opcode:指定beq或bne
rs 、 rt:确定要参与比较的寄存器
immediate:内存的地址有32bits,immediate只有16bits,不够表示整个目标地址(绝对地址)。
条件分支语句 if-else / while / for,通常循环的内容都比较少,最多50行指令。(注意jump不是分支语句)

总结:分支通常和程序计数器的位置差不太多。
解决方法:根据相对于PC的偏移量,确定目标地址
目标地址 = PC + immediate (immediate是一个有符号 2s complement整数)
分支计算

immediate 表示要跳多少个words,这个是和需要跳掉的指令数一样的immediate 可正可负

immediate 是要对当前PC加/减多少个指令数(从当前指令结束地址开始,进入分支)。End = (PC + 4) + (immediate * 4)所以,



此时的immediate为-4。(因为第四条已经走完了,要往回走四个指令才到第一条指令头)


PC+4中获得这四位比特位。 ⇒ 这意味着我们其实不能访问内存中的任何位置,但这在大多数情况下都足够了。jr指令,jump-to- register,跳到某个寄存器,目标地址会用整个寄存器来保存。

如果在i-format的语句中,想要跳转到超过16bits地址的位置怎么办?如beq $s0, $s1, L1,其中L1超过了PC可以支持的范围。
参考StackOverflow答案
如果 I-format 命令的 16 位对于 L1 来说还不够,您可以使用 J-format,因为它有 26 位用于您的地址(只需围绕它构建您的 if)。
如果这还不够,您应该使用以下命令将地址保存到寄存器中:la $t0, L1然后使用以下命令跳转到该寄存器:jr $t0如果您首先将其安全地保存到寄存器中,那么您将拥有完整的 32 位地址。
Register addressing:操作数是寄存器。
Immediate addressing :操作数是一个位于指令内的常量。
base addressing (displacement addressing) :操作数在【一个寄存器 + 指令里的一个常数】所映射的内存地址里。(如lw和sw)
PC-relative addressing:【PC + 指令里的一个常数】(如beq和bne)
Pseudo-direct addressing:【PC的前四位】+【指令里的26位地址】+【00】
每条MIPS指令由32位比特组成

pseudo-direct addressing
DataPath DesignBuilding a Processor: Datapath & Control处理器中两个核心组成部分:
Datapath
Control
MIPS Processor: Implementation核心MIPS ISA子集的最简单实现应该包括:
(位运算指令 sll/srl等和J-type指令这里不做讨论)
Instruction Execution Cycle (Basic)
Fetch instructions
Decode operation
Operand Fetch
Execute
Result Write
MIPS Instruction Execution下面展示了三种MIPS代表性的指令的实施步骤(获取和解码的步骤没有展示,假设已按标准流程处理):

opr = operand
ofst = offset
MemAddr = Memory Address
另一种设计:
解码和获取操作数的过程(解码在MIPS中比较简单)执行过程分成ALU(计算Calculation)和内存读取Memory Access
维基百科
ALU: 在计算中,算术逻辑单元( ALU ) 是一种组合 数字电路,它对整数二进制数执行算术和按位运算。
Build a MIPS Processor我们要做的事:
Fetch StageRequirements指令获取阶段
输出到下一阶段(解码 Decode)
- 指令会被执行

图中的各部分解释:
指令内存 Instruction Memory
Sequential circuit (后面会解释)Clock signal(图中没展示)
加法运算器 Adder

A BA + B时钟的概念
clock period”读取程序计数器PC,然后在“下一个时钟的上升沿next rising clock edge”将其更新为PC+4(图中红色箭头)。(也就是没有看到上升信号之前,都不能把PC+4后的结果更新上去)
阅读资料:深入浅出计算机组成原理学习笔记
Clocking视频解读: Crash Course Computer Science - episode 7
Crash Course Computer Science - episode 8
Decode Stageopcode,确定指令类型和域的长度Fetch得到输入,指令将会被执行ALU,对操作数进行对应的操作。
组成元素:
Register File
RegWrite 是一个控制信号


如果按r-format的处理方式,读入的数据1/2应该是rs/rt的位置,而i-format的处理方式中,读入的数据应该是rs和immediate。
因此,如果不改变读取的索引,就会读错数据。


Multiplexercontrol = i,选择第i个输入线路维基百科
在电子技术(特别是数字电路)中,多路复用器(multiplexer / mux),也称为数据选择器(Data Selector),是一种可以从多个模拟或数字输入信号中选择一个信号进行输出的器件。
一个有 2 n 2^n 2n 输入端的数据选择器有 n 个可选择的输入-输出线路,可以通过控制端来选择其中一个信号被选择作为输出。
数据选择器主要用于增加一定量的时间和带宽内的可以通过网络发送的数据量。

这里的符号扩展Sign Extend是什么意思?
因为要与之相加的数据1是32位的,而immediate value只有16位,因此要将其扩展为32位。
那么怎么才不会改变原有数据的信息呢?⇒ 扩展的比特等于该数字的符号位。
例如:
-50 ⇒ ···· ···· ···· ···· 1110 0111 1010 0011 (符号位为第一个比特,即1)
扩展为32比特 1111 1111 1111 1111 1110 0111 1010 0011
+17 ⇒ ···· ···· ···· ···· 0000 0000 0000 1001 (符号位为第一个比特,即0)
扩展为32比特 0000 0000 0000 0000 0000 0000 0000 1001
加了多路复用器之后,我们就可以让他来决定,读取的是第二操作数(寄存器)(r-format)还是扩展为32比特的常量数immediate(i-format)。

Load Word Instruction第一个多路复用器选择rt作为写入的寄存器,第二个多路复用器选择扩展为32位的常量数和第一操作数($22,原始寄存器)做运算。

Branch Instruction0读取写寄存器。但是我们不会在分支指令里执行写操作,所以这里没什么关系。拓展为32位的常量数immediate输出,但在beq里,我们需要去比较$9和$0寄存器里的数(也就是read data 1 & 2),因此不能选择immediate,要选择read data2输出。3去哪了呢?⇒ 发给程序计数器PC了(在execute阶段会详细解读)。
ALU StageRequirementsArithmetic-Logic Unit ,算术逻辑单元Execution stage
non-branch instruction
在下面这个例子中,
0010(根据上表可查得),将10和12相加,输出22 (ALU result) 和不为零(isZero?)。ALUControl用的是opcode+func field。

branch instruction分支运算比执行两数相加难得多。
例如, beq $9, $0, 3
isZero信号来表示两者是否相等(如果相等,isZero=1,否则为0)例子:

分支阶段运算步骤:
$9 $0两个寄存器取数,取到的数字为read data1和read data2。其中,read data1直接读入ALU,read data2还需要经过第二个复用器确认。第一个复用器确定当前不需要写入数据,将寄存器写标识RegWrite置为0。0110),如果相减结果为0,说明两者相等,isZero置为1,否则置为0。left shift 2-bit,得到4倍的immediate(4 * immediate),也即当前指令到达目标地址的word位移*4(乘以四换成byte),此时再加上当前指令的下一条指令地址(PC+4)的绝对地址,就得到目标地址的绝对地址了。⇒ 也就是说,目标地址的绝对地址为 (PC+4) + immediate * 4。PCSrc等于isZero的值 (⚠️注意PCSrc不是和isZero直接连起来的,只是在beq指令下他们相等,否则执行bne的时候就会出错了) ,Memory StageRegister WriteRegister Write):结果会被存储起来。
MemWrite = 1, MemRead = 0,此时会将write date写入到目标地址MemWrite = 0, MemRead = 1,此时会从目标地址读取数据load instruction例子,lw $21, -50($22)

步骤:
假设22号寄存器里存着数字98,21号寄存器里存着数字15。
MemWrite = 0, MemRead = 1,执行内存读取,将48读到写寄存器$21(write register $21)。write instruction
步骤:
假设22号寄存器里存着数字98,21号寄存器里存着数字15。我们要把21号寄存器的内容存到-50+[$22] = 48的内存地址中。
RegWrite = 0。read data1为read register1里的数据98,read data2 为 read register2里的数据15, read data2传到数据内存中的write data。ALUSrc = 1,选择符号拓展后的32-bit immediate value -50MemWrite = 1, MemRead = 0,执行写入内存的操作,将数字15写入内存地址48。Non-Memory Instruction
步骤:
假设9号寄存器里存着数字12,10号寄存器里存着数字27。
RegDst = 1,即选择rd作为写入的寄存器write register。ALUSrc = 0)MemWrite = 0, MemRead = 0,不做任何操作。MemReg = 0,不读入,而是选择ALU计算的结果。【注意,这个复用器MUX是倒过来放的,其他MUX都是上面是0下面是1,它是上面为1下面为0。】Register Write Stage

步骤:
假设9号寄存器里存着的数字是10,10号寄存器里存着的数字是12。
ALUSrc = 0,选择read data2传给ALU,与read data1进行加法运算。得到结果10+12=22。MemWrite = 0, MemRead = 0,不对数据内存做任何操作,将一个未知的数据传给第三个复用器,22也传给第三个复用器。MemToReg = 0,不将数据从内存写入寄存器,而是选择22,传给write data,写入write register。The Complete Datapath
Processor ControlIdentidied Control Signals
Generating Control Signals: Idea控制信号是根据要执行的指令生成的:
opcode -> 指令格式
例子:R-format指令 ⇒ RegDst = 1(使用Inst[15:11])
R-type指令有一些其他的信息:6-bit的funct域(function code,Inst[5:0])
Idea:设计一个组合电路combinational circuit来根据opcode (也有可能有function code)生成这些信号——这需要一个控制单元
The Control Unit
控制单元就是用来控制这些“选择”的。
实现控制单元的方法:
Control Signals回顾一下之前的一些MIPS指令集:

RegDst要写入哪个寄存器

RegWrite是否写入寄存器

ALUSrc
MemRead
MemWrite
MemToReg注意这里的MUX是倒过来放的。

PCSrc
现在还有ALUControl信号没讨论,下一节会说到。
现在得到的结论有:

ALUControl Signal我们进一步观察 ALU:

Ainvert:当此值为1时,对A取反Binvert:当此值为1时,对B取反Operation:2个bit,选择三个结果的其中之一





了解了一位bit的ALU如何设计,我们将其抽象成一个黑箱,再去看看四位bit的ALU如何设计?把四个1-bit ALU串起来:


理解了4-bit ALU如何设计后,将其推广至32-bit也是一个道理。
好了,现在我们可以开始设计ALUControl信号了,设计它需要【6-bit 的 opcode区域和6-bit 的 function code区域】。
ALUop】首先我们有个简单的想法:
1. 使用opcode来生成一个2-bit的ALUop信号,用于表示指令属于哪一种类型(内存/分支/r-type)

2. 使用ALUop信号和function code域(这个只有r-type要用到)来生成4-bit的ALUControl信号。

ALUControl信号】lw/sw:对于内存指令,我们不需要关注function code。我们需要将rs中的值和immediate相加得到地址,因此ALU的操作是add,ALUcontrol的信号则应该为 0010(见右下的表)。beq:对于分支指令beq,我们也不需要关注function code。我们需要将两寄存器中的值相减,以比较二者是否相等,因此ALU的操作是subtract,ALUcontrol的信号则应该为 0110。R-type:对于r-type类型的指令,他想要做什么操作,就可以对应查MIPS指令表得到对应的操作控制信号。比如想要相加,ALUcontrol的信号则应该为 0010。
ALU Control Unit】funct field and 2-bit ALUop我们要寻找一种最简单的表达式,来通过input确定唯一的output。(表中的X表示我们不用关心这里的值是什么)

ALUControl_i 表示第i位的ALUControl信号如何表示(注意,i是从右到左的 ⇒ 图中顺序是 3 2 1 0),a' 表示对该数取反,a · b表示a and b,a + b表示a or b。
ALUControl_3 = 0
ALUControl_2 = ALUop_0 + (ALUop_1 · F1)= ALUop_0 || (ALUop_1 && F1)
ALUControl_1 = (ALUop_1' · F2)' = ALUop_1' or F2' = -ALUop_1 or -F2 = 第一位op取反 or 第二位F取反
ALUControl_0 = (ALUop_1 · (F0 + F3)) = ALUop_1 && (F0 || F3) = ALUop_1为1 同时F0 F3其中一个为1
从而,我们得到简单的组合逻辑:

举例:sub

我们现在分析完了所有单独的信号和他们应该有的值,那么现在可以来设计controller了。
通常电路设计的步骤是:
- 填写
truth table
- input:opcode
- output: 各种控制信号- 找到每一个信号的简化的表示






生成一个信号,当这个指令是xxx时,信号为1 ⇒ 使用inverter(01反转器, 图中的圆圈)
电路设计如下:





Instruction Execution指令执行=
这些执行过程都在一个时钟周期内:

假设延迟可忽略不计,计算周期时间,可以得到(单位:ns):
【内存操作2ns,ALU/adders 2ns,读取寄存器文件1ns】

如果我们将一整个过程的时长作为时钟周期,我们至少要将周期设为8ns(因为根据“水桶原则”,我们要考虑的是这里面的最短板,也就是用时最长的那个),这就会让每个指令的周期时间很长。
我们将执行的步骤划分为多个小块,每块的周期都设为2ns:
那么,各指令的时长将变为:

看起来总的时间好像变长了,这就需要用到我们进一步的解决方案。
也就是我们可以同时进行(不同指令的)这几个步骤(1. 取指令 2. 指令解码和寄存器读取 3. ALU 运算 4. 内存读/写 5. 寄存器写入)
详见下一章节。
引例:洗衣服
分段工作:一个完整的任务完成再下一个。

流水线:每个“资源”被使用完毕后可以被下一个任务所使用。

latency,但可以提升整体工作的吞吐量delays:
dependency停顿IF, Instruction Fetch:指令获取ID, Instruction Decode and Register Read:指令解码、读取寄存器EX, Execute an operation or calculate an address:执行一个操作或计算一个地址MEM, Access an operand in data memory:访问数据内存中的一个操作数WB, Write back the result into a register:将结果写回寄存器
Pinelined Execution多个指令同时在管道中。

MIPS Pineline: Datapath单一周期 Single-cycle
流水线式 Pinelined
下一条指令要用到的数据,存储在程序员可以看到的状态元件里(PC、寄存器文件register file、数据内存data memory)
同一条指令在下一个pipeline阶段要用到的数据,存储在额外的寄存器例(流水线专用寄存器pinepline registers)
IF/ID:在IF ID之间的寄存器ID/EX:在ID EX之间的寄存器EX/MEM:在EX MEM之间的寄存器MEM/WB:在MEM WB之间的寄存器图中有颜色的就是pipeline寄存器。


在时钟周期的结束阶段,IF/ID获取(存储):








这里有个问题是,此时写回的寄存器号码已经更改了(因为后续的pipeline会覆盖前面的结果)。
Corrected Datapathwrite register”的号码

Pipeline Controlsingle-cycle datapath一样的控制信号





Cycle time :
m个指令的执行时间:
举例:

CycleTime需要选最长的单个指令执行时间,在例子中就是8ns
执行100条指令所需时间:100 * 8ns = 800ns
Cycle time :
m个指令的执行时间:
举例,

CycleTime:选择其中最长的阶段时间 = 2ns
执行100条指令,给定average CPI 为4.6:
100
∗
4.6
∗
2
n
s
=
920
n
s
100 * 4.6 * 2ns = 920ns
100∗4.6∗2ns=920ns
Cycle time :
pipeline overheadm个指令的执行周期数量:
m
+
N
−
1
m + N - 1
m+N−1 (其中N-1是填满流水线最基本的“浪费”)
m个指令的执行时间:
T
i
m
e
p
i
p
e
l
i
n
e
=
C
y
c
l
e
∗
C
T
p
i
p
e
l
i
n
e
=
(
m
+
N
−
1
)
∗
(
m
a
x
(
T
k
)
+
T
d
)
Time_{pipeline} = Cycle * CT_{pipeline} = (m + N - 1) * (max(T_k) + T_d)
Timepipeline=Cycle∗CTpipeline=(m+N−1)∗(max(Tk)+Td)
举例:

CycleTime:
假设pipeline register的延迟是0.5ns
longest stage time + overhead = 2 + 0.5 = 2.5 ns
执行100条指令所需时间:
(
100
+
5
−
1
)
∗
2.5
n
s
=
260
n
s
(100 + 5 - 1) * 2.5ns = 260ns
(100+5−1)∗2.5ns=260ns
理想化的假设:
S p e e d u p p i p e l i n e = T i m e s e q / T i m e p i p e l i n e = ( m ∗ ∑ k = 1 N T k ) / ( ( m + N − 1 ) ∗ ( m a x ( T k ) + T d ) ) = ( m ∗ N ∗ T k ) / ( m + N − 1 ) ∗ T k ≈ ( m ∗ N ∗ T k ) / ( m ∗ T k ) ≈ N Speeduppipeline=Timeseq/Timepipeline=(m∗N∑k=1Tk)/((m+N−1)∗(max(Tk)+Td))=(m∗N∗Tk)/(m+N−1)∗Tk≈(m∗N∗Tk)/(m∗Tk)≈N Speeduppipeline=Timeseq/Timepipeline=(m∗k=1∑NTk)/((m+N−1)∗(max(Tk)+Td))=(m∗N∗Tk)/(m+N−1)∗Tk≈(m∗N∗Tk)/(m∗Tk)≈N
总结:
Pipeline processor可以获得N倍的速度提升,其中N是pipeline阶段的数量。

寄存器位于处理器的数据路径中。 如果操作数在内存中,我们必须将它们加载到处理器(寄存器),对它们进行操作,然后将它们存储回内存。

flip-flops电路(触发器)
使用内存技术的层次结构:
将经常和最近使用的数据保存在更小但更快的内存中,
仅当在更快的内存中找不到数据/指令时,再去更大更慢的内存中查。
在很小的时间间隔内,程序只访问一小部分内存地址空间。

时间布局Temporal locality:如果一个项目被引用,它往往会很快再次被引用
空间布局Spatial locality:如果一个项目被引用,附近的项目将很快被引用
不同的布局为指令和数据。

不同的执行阶段可能使用不同的工作集。我们的目标是发现工作集并将其保存在最靠近 CPU 的内存中。
如何让 较慢的 主内存显得更快?
缓存——靠近 CPU 的小而快的 SRAM
硬件管理:对程序员透明
如何使 较小的 主内存看起来比实际更大?
虚拟内存
操作系统管理:对程序员透明
Memory Access TimeHit:数据在缓存中。
Hit rate:命中的内存访问比例Miss:数据不在缓存中。
平均访问时间 = 命中率 x 命中时间 + (1-命中率) x 未命中惩罚
例子:假设我们的片上 SRAM(高速缓存)的访问时间为 0.8 ns,但我们可以获得的最快的 DRAM(主存储器)的访问时间为 10ns。 我们需要多高的命中率才能维持 1ns 的平均访问时间?
Let h be the desired hit rate.
1 = 0.8h + (1 – h) * (10 + 0.8) = 0.8h + 10.8 – 10.8h
10h = 9.8 -> h = 0.98
Hence we need a hit rate of 98%.
Memory to Cache Mapping缓存块/行:内存和缓存之间的传输单位
块大小通常是一个或多个word
例如:16 字节的块 约等于 4 字块;32 字节的块 约等于 8 字块
为什么块大小 大于 字大小?



观察:
2N 字节块在 2N 字节边界处对齐
2N 字节块内的字地址具有相同的 (32-N) 最高有效位 (MSB)。
位 [31:N] -> 块号
位 [N-1:0] -> 块内的字节偏移量