#include
using namespace std;
int main()
{
int a = 6, b = 9;
int prod = a * b; // 计算 a 和 b 的乘积,用于之后计算最小公倍数
while (b)
{
int temp = b;
b = a % b;
a = temp;
}
cout << "最大公约数: " << a << endl << "最小公倍数: " << prod / a << endl;
return 0;
}
.text
main:
# 求 a 和 b 的最大公约数及最小公倍数
ADDI $r1, $r0, 6 # r1: a
ADDI $r2, $r0, 9 # r2: b
MULT $r1, $r2 # a * b -> (LO, HI)
MFLO $r5 # a * b -> r5
Loop:
BLEZ $r2, EXIT
DIV $r1, $r2 # a / b -> (LO, HI)
ADD $r1, $r2, $r0 # a = b
MFHI $r2 # b = a % b
B Loop
EXIT:
# 计算最小公倍数
DIV $r5, $r1
MFLO $r6 # r6 -> 最小公倍数 (a * b / 最小公约数)
# r1 -> 最大公约数
TEQ $r0, $r0
PC
的值为 0x00000000
ADDI $r1, $r0, 6
;观察到寄存器 r1
的值变为了 6,即 r1
中存储数 a a a 的值。同时 PC
值加 4,这是因为 MIPS 采用定长指令格式,每条指令长度均为 4 字节,因此如果没有跳转指令的话,每顺序执行一条指令 PC
的值都会加 4ADDI $r2, $r0, 9
;观察到寄存器 r2
的值变为了 9,即 r2
中存储数 b b b 的值MULT $r1, $r2
;观察到寄存器 LO
的值变为了 0x36,即 a × b a\times b a×b 的值 54MFLO $r5
;观察到寄存器 r5
的值变为了 54,即将寄存器 LO
的值传送到寄存器 r5
BLEZ $r2, 4
;其测试条件为 r2
的值小于等于 0,如果满足则跳转到目标地址 0x00A1001A
处,也即当前指令地址 + 5 条指令的偏移量后得到的地址 (4 条指令的偏移量 + PC 原本就要增加的 1 条指令的偏移量);同时注意到标号 EXIT
被替换为了相对 PC 值的偏移量 4;此时不满足条件,因此不跳转,可以看到 PC 值为 0x00000014
DIV $r1, $r2
,可以看到,寄存器 LO
值为 0,HI
值为 6,表明除法后的余数为 6,商为 0ADD $r1, $r2, $r0
,可以看到,r1
的值变为了 r2
的值 9MFHI $r2
,可以看到,r2
的值变为了刚才计算得到的余数 6;至此已完成了辗转相除法的第一轮循环BEQ $r0, $r0, Loop
;注意到这条机器指令是由无条件跳转指令 B Loop
编译得到的,它的判断条件 r0 = r0
永远为真,因此总是跳转到目标地址 Loop
处;可以观察到,指令执行完毕后,PC
变为了标号 Loop
的地址 0x00000010
r2 <= 0
条件成立,下面直接打上断点,执行到 r2 <= 0
条件成立的时候,如下图所示,此时 r2 == 0
,因此跳转条件满足。同时注意到 r1 = 3
,这里 r1
保存的即为计算完的最大公约数;指令执行完毕后,PC
值变为 0x00000024
,表明成功跳转至指令 DIV $r5, $r1
DIV $r5, $r1
,即用原来两个数的积除以最大公约数,此时 LO
为 0x12
,HI
为 0,表明商为 18,余数为 0,这个 18 即为计算出的最小公倍数MFLO $r6
,r6
的值变为 LO
的值 18,此时 r6
用于保存最后得到的最小公倍数这两个实验都是优化同一段程序,因此就把报告写在一起了
0xA0
处的数据转换为 10 进制形式显示,转换后的数字存储在内存地址 0xA8
中# v1: data hazard + control hazard
.text # 将 16 进制数转为 10 进制数进行显示
main:
ADDIU $r8, $r0, 160 # r8 = 160 (0xA0) ; 要转换的数(半字,16位,小端对齐)存在内存地址 160 处
ADDIU $r9, $r0, 168 # r9 = 168 (0xA8) ; 转换完毕的数存在内存地址 168 处
LHU $r1, 0($r8) # r1 = memory[r8]
ADDIU $r10, $r0, 10 # r10 = 10
ADDIU $r4, $r0, 0
loop1:
DIVU $r1, $r10
MFHI $r2 # r2 = r1 % 10
SLLV $r2, $r2, $r4 # r2 左移 r4 位
ADDIU $r4, $r4, 4 # r4 += 4
OR $r3, $r2 # r3 |= r2
MFLO $r1 # r1 /= 10
# stall (RAW)
BGTZ $r1, loop1 # while(r1 > 0)
SW $r3, 0($r9) # 存字
TEQ $r0, $r0
0xA0
内的数据改为 0xA0
,即十进制数 160ADDIU $r8, $r0, 160
与 LHU $r1, 0($r8)
存在 RAW 冲突,LHU $r1, 0($r8)
必须 stall 一个周期ADDIU $r10, $r0, 10
与 DIVU $r1, $r10
存在 RAW 冲突,DIVU $r1, $r10
必须 stall 一个周期MFHI $r2
与 SLLV $r2, $r2, $r4
存在 RAW 冲突,SLLV $r2, $r2, $r4
必须 stall 两个周期SLLV $r2, $r2, $r4
与 OR $r3, $r2
存在 RAW 冲突,OR $r3, $r2
必须 stall 一个周期MFLO $r1
与 BGTZ $r1, loop1
存在 RAW 冲突,BGTZ $r1, loop1
必须 stall 两个周期BGTZ $r1, loop1
与之后执行的指令存在控制冲突,后续指令必须等到 BGTZ $r1, loop1
的 ID 段结束,也就是判断出分支是否跳转以及跳转的目的地址之后才能继续执行。这里 stall 了一个周期这里并没有结构冲突;如果有结构冲突的话,可以通过重复设置功能部件来解决
ADDIU $r8, $r0, 160
与 LHU $r1, 0($r8)
存在的 RAW 冲突为例,当开启定向技术后,LHU $r1, 0($r8)
不再需要 stall 一个周期,而是直接由流水寄存器 MEM/WB.ALUOUT
取得需要的 r8
的值,进而在 EX 段进行计算;其他 RAW 冲突的解决也同理MFLO $r1
移到了前面,让它尽快将 r1
中的数算出,以避免 RAW 冲突# v2: software scheduling -> eliminate data hazards
.text # 将 16 进制数转为 10 进制数进行显示
main:
ADDIU $r8, $r0, 160 # r8 = 160 (0xA0) ; 要转换的数(半字,16位,小端对齐)存在内存地址 160 处
ADDIU $r9, $r0, 168 # r9 = 168 (0xA8) ; 转换完毕的数存在内存地址 168 处
LHU $r1, 0($r8) # r1 = memory[r8]
ADDIU $r10, $r0, 10 # r10 = 10
ADDIU $r4, $r0, 0
loop1:
DIVU $r1, $r10
MFHI $r2 # r2 = r1 % 10
MFLO $r1 # r1 /= 10 # 将本指令提前执行
SLLV $r2, $r2, $r4 # r2 左移 r4 位
ADDIU $r4, $r4, 4 # r4 += 4
OR $r3, $r2 # r3 |= r2
BGTZ $r1, loop1 # while(r1 > 0)
SW $r3, 0($r9) # 存字
TEQ $r0, $r0
# v2: loop unrolling to avoid loop overheads
.text # 将 16 进制数转为 10 进制数进行显示
main:
ADDIU $r8, $r0, 160 # r8 = 160 (0xA0) ; 要转换的数(半字,16位,小端对齐)存在内存地址 160 处
ADDIU $r9, $r0, 168 # r9 = 168 (0xA8) ; 转换完毕的数存在内存地址 168 处
LHU $r1, 0($r8) # r1 = memory[r8]
ADDIU $r10, $r0, 10 # r10 = 10
ADDIU $r4, $r0, 0
# unroll the loop 3 times (Assuming the decimal number has 3n bits) and omit the register renaming
# can decrease the loop overheads
loop1:
DIVU $r1, $r10
MFHI $r2 # r2 = r1 % 10
MFLO $r1 # r1 /= 10
SLLV $r2, $r2, $r4 # r2 左移 r4 位
ADDIU $r4, $r4, 4 # r4 += 4
OR $r3, $r2 # r3 |= r2
DIVU $r1, $r10
MFHI $r2 # r2 = r1 % 10
MFLO $r1 # r1 /= 10
SLLV $r2, $r2, $r4 # r2 左移 r4 位
ADDIU $r4, $r4, 4 # r4 += 4
OR $r3, $r2 # r3 |= r2
DIVU $r1, $r10
MFHI $r2 # r2 = r1 % 10
MFLO $r1 # r1 /= 10
SLLV $r2, $r2, $r4 # r2 左移 r4 位
ADDIU $r4, $r4, 4 # r4 += 4
OR $r3, $r2 # r3 |= r2
BGTZ $r1, loop1 # while(r1 > 0)
SW $r3, 0($r9) # 存字
TEQ $r0, $r0
BGTZ $r1, loop1
与 OR $r3, $r2
交换顺序,这就相当于手动将 OR $r3, $r2
调度到了分支延迟槽中。这样当开启分支延迟槽时,在 BGTZ $r1, loop1
未决定是否跳转以及跳转的地址时,系统就会自动执行 OR $r3, $r2
以避免产生 stall# v4: specialized for delayed branch
.text # 将 16 进制数转为 10 进制数进行显示
main:
ADDIU $r8, $r0, 160 # r8 = 160 (0xA0) ; 要转换的数(半字,16位,小端对齐)存在内存地址 160 处
ADDIU $r9, $r0, 168 # r9 = 168 (0xA8) ; 转换完毕的数存在内存地址 168 处
LHU $r1, 0($r8) # r1 = memory[r8]
ADDIU $r10, $r0, 10 # r10 = 10
ADDIU $r4, $r0, 0
# fill branch delay slot from before
loop1:
DIVU $r1, $r10
MFHI $r2 # r2 = r1 % 10
MFLO $r1 # r1 /= 10
SLLV $r2, $r2, $r4 # r2 左移 r4 位
ADDIU $r4, $r4, 4 # r4 += 4
BGTZ $r1, loop1 # while(r1 > 0)
OR $r3, $r2 # r3 |= r2
SW $r3, 0($r9) # 存字
TEQ $r0, $r0
OR $r3, $r2
以填补计算分支指令是否跳转的时间间隔DIVU $r1, $r10
移动到了分支指令之后,同时在循环之前增添了一条 DIVU $r1, $r10
指令,这就相当于手动将目标处指令 DIVU $r1, $r10
调度到了分支延迟槽中。这样当开启分支延迟槽时,在 BGTZ $r1, loop1
未决定是否跳转以及跳转的地址时,系统就会自动执行 DIVU $r1, $r10
以避免产生 stall# v4: specialized for delayed branch
.text # 将 16 进制数转为 10 进制数进行显示
main:
ADDIU $r8, $r0, 160 # r8 = 160 (0xA0) ; 要转换的数(半字,16位,小端对齐)存在内存地址 160 处
ADDIU $r9, $r0, 168 # r9 = 168 (0xA8) ; 转换完毕的数存在内存地址 168 处
LHU $r1, 0($r8) # r1 = memory[r8]
ADDIU $r10, $r0, 10 # r10 = 10
ADDIU $r4, $r0, 0
# fill branch delay slot from target
DIVU $r1, $r10
loop1:
MFHI $r2 # r2 = r1 % 10
MFLO $r1 # r1 /= 10
SLLV $r2, $r2, $r4 # r2 左移 r4 位
ADDIU $r4, $r4, 4 # r4 += 4
OR $r3, $r2 # r3 |= r2
BGTZ $r1, loop1 # while(r1 > 0)
DIVU $r1, $r10
SW $r3, 0($r9) # 存字
TEQ $r0, $r0
DIVU $r1, $r10
以填补计算分支指令是否跳转的时间间隔SW $r3, 0($r9)
调度到了分支延迟槽中。这样当开启分支延迟槽时,在 BGTZ $r1, loop1
未决定是否跳转以及跳转的地址时,系统就会自动执行 SW $r3, 0($r9)
以避免产生 stall# v4: specialized for delayed branch
.text # 将 16 进制数转为 10 进制数进行显示
main:
ADDIU $r8, $r0, 160 # r8 = 160 (0xA0) ; 要转换的数(半字,16位,小端对齐)存在内存地址 160 处
ADDIU $r9, $r0, 168 # r9 = 168 (0xA8) ; 转换完毕的数存在内存地址 168 处
LHU $r1, 0($r8) # r1 = memory[r8]
ADDIU $r10, $r0, 10 # r10 = 10
ADDIU $r4, $r0, 0
# fill branch delay slot from fall through
loop1:
DIVU $r1, $r10
MFHI $r2 # r2 = r1 % 10
MFLO $r1 # r1 /= 10
SLLV $r2, $r2, $r4 # r2 左移 r4 位
ADDIU $r4, $r4, 4 # r4 += 4
OR $r3, $r2 # r3 |= r2
BGTZ $r1, loop1 # while(r1 > 0)
SW $r3, 0($r9) # 存字
TEQ $r0, $r0
SW $r3, 0($r9)
以填补计算分支指令是否跳转的时间间隔CC 代表 Clock