本人菜鸡一个,在csdn上各位大佬的blog 指点下磕磕绊绊完成了computer architecture的实验,前后历时半个来月,无以为报,献上自己的实验报告,由于我大二下上的网课一直在摸鱼...许多知识点掌握的不是很牢,其中可能会有些错误,也有很多不详细的地方,欢迎大佬指正,也希望能对后人有点帮助,毕竟我一开始做这玩意毫无头绪浪费了不少时间
Write a Fibonacci.c storing the first ten sequence numbers. Run the Fibonacci program in your computer, analyze the assembly code for the Fibonacci.c.
由于没有输出函数,生成.EXE时临时加入system(”pause”)以证明运行成功
c语言编译为x86汇编语言
汇编完整代码如下:
.file "Fibonacci.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB25:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $48, %esp
call ___main
movl $10, 44(%esp)
movl $1, 8(%esp)
movl 8(%esp), %eax
movl %eax, 4(%esp)
movl $2, 44(%esp)
jmp L2
L3:
movl 44(%esp), %eax
subl $1, %eax
movl 4(%esp,%eax,4), %edx
movl 44(%esp), %eax
subl $2, %eax
movl 4(%esp,%eax,4), %eax
addl %eax, %edx
movl 44(%esp), %eax
movl %edx, 4(%esp,%eax,4)
addl $1, 44(%esp)
L2:
cmpl $9, 44(%esp)
jle L3
movl $0, %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE25:
.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0"
汇编代码分析
我的cpu为amd的cpu,故格式与intel不同,汇编指令为AT&T格式
第一部分:声明
.file "Fibonacci.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
为对整个程序中函数的声明定义,无实际意义
第二部分:main函数
_main:
LFB25:
.cfi_startproc //用在函数开头作为进入主函数的标志
pushl %ebp //存入栈底
.cfi_def_cfa_offset 8
.cfi_offset 5, -8 //执行完pushl %ebp后SP与CFA偏了8字节(4字节return address,4字节ebp)
movl %esp, %ebp
.cfi_def_cfa_register 5//寄存器不变,偏移量变为5
andl $-16, %esp
//将寄存器里存的4字节地址与0xfffffff0按位&,并将结果存在寄存器%esp中,调整栈指针的地址
subl $48, %esp//申请48个字节
call ___main
movl $10, 44(%esp)//int i=10;
movl $1, 8(%esp)//Fibonacci[0]=1;
movl 8(%esp), %eax//eax暂存数据
movl %eax, 4(%esp)//Fibonacci[1]=1;
movl $2, 44(%esp)//i=2;
jmp L2
第三部分:L3(对应for循环)
L3:
movl 44(%esp), %eax
subl $1, %eax
movl 4(%esp,%eax,4), %edx //(sep+4*eax+4)放入edx。
//由该步骤推出上两步作用在于用eax寄存器设定可变偏移量
movl 44(%esp), %eax
subl $2, %eax
movl 4(%esp,%eax,4), %eax
//此三步同理
addl %eax, %edx
//L2中上述步骤实现Fibonacci[i]=Fibonacci[i-1]+Fibonacci[i-2];
movl 44(%esp), %eax//eax重新置入i=2
movl %edx, 4(%esp,%eax,4)//edx放入(sep+4*eax+4)
addl $1, 44(%esp)//i++;
第四部分:结束与循环判断
L2:
cmpl $9, 44(%esp)
jle L3//44(%esp)处数据小于等于9则跳转回L2,对应for函数
movl $0, %eax//结束eax置0
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
Observe the memory areas in bytes for the Fibonacci numbers given by the computer where the Fibonacci program running. Analyze what you have seen.
Int Fibonacci[10];
EIP寄存器的值增加,增加大小的就是读取指令的字节大小
Fibonacci[0]=Fibonacci[1]=1;
movl %esp, %ebp
.cfi_def_cfa_register 5//寄存器不变,偏移量变为5
andl $-16, %esp
//将寄存器里存的4字节地址与0xfffffff0按位&,并将结果存在寄存器%esp中,调整栈指针的地址
subl $48, %esp//申请48个字节
call ___main
movl $10, 44(%esp)//int i=10;
movl $1, 8(%esp)//Fibonacci[0]=1;
movl 8(%esp), %eax//eax暂存数据
movl %eax, 4(%esp)//Fibonacci[1]=1;
movl $2, 44(%esp)//i=2;
jmp L2
For循环
For循环中eax寄存器存放变量i,edx为数组中第i个数,故每轮循环中eax+1,edx呈斐波那契数列。
L3:
movl 44(%esp), %eax
subl $1, %eax
movl 4(%esp,%eax,4), %edx //(sep+4*eax+4)放入edx。
//由该步骤推出上两步作用在于用eax寄存器设定可变偏移量
movl 44(%esp), %eax
subl $2, %eax
movl 4(%esp,%eax,4), %eax
//此三步同理
addl %eax, %edx
//L2中上述步骤实现Fibonacci[i]=Fibonacci[i-1]+Fibonacci[i-2];
movl 44(%esp), %eax//eax重新置入i=2
movl %edx, 4(%esp,%eax,4)//edx放入(sep+4*eax+4)
addl $1, 44(%esp)//i++;
结束
L2:
cmpl $9, 44(%esp)
jle L3//44(%esp)处数据小于等于9则跳转回L2,对应for函数
movl $0, %eax//结束eax置0
leave
观察可知,代码运行中寄存器状态变化符合上述对汇编代码观察得到的结论
Cross compile the Fibonacci.c for RISC-V targets, or write the assembly code of Fibonacci.c in RISC-V assembly language, and run it in RISC-V online simulator (www.kvakil.me/venus/). Observe the memory areas in bytes for the Fibonacci numbers given by the RISC-V simulator.
交叉编译平台:Compiler Explorer
交叉编译结果:
Machine Code | Basic Code | Original Code |
0xfc010113 | addi x2 x2 -64 | addi sp,sp,-64 |
0x02812e23 | sw x8 60(x2) | sw s0,60(sp) |
0x04010413 | addi x8 x2 64 | addi s0,sp,64 |
0x00100793 | addi x15 x0 1 | li a5,1 |
0xfcf42423 | sw x15 -56(x8) | sw a5,-56(s0) |
0xfc842783 | lw x15 -56(x8) | lw a5,-56(s0) |
0xfcf42223 | sw x15 -60(x8) | sw a5,-60(s0) |
0x00200793 | addi x15 x0 2 | li a5,2 |
0xfef42623 | sw x15 -20(x8) | sw a5,-20(s0) |
0x0580006f | jal x0 88 | j .L2 |
0xfec42783 | lw x15 -20(x8) | lw a5,-20(s0) |
0xfff78793 | addi x15 x15 -1 | addi a5,a5,-1 |
0x00279793 | slli x15 x15 2 | slli a5,a5,2 |
0xff078793 | addi x15 x15 -16 | addi a5,a5,-16 |
0x008787b3 | add x15 x15 x8 | add a5,a5,s0 |
0xfd47a703 | lw x14 -44(x15) | lw a4,-44(a5) |
0xfec42783 | lw x15 -20(x8) | lw a5,-20(s0) |
0xffe78793 | addi x15 x15 -2 | addi a5,a5,-2 |
0x00279793 | slli x15 x15 2 | slli a5,a5,2 |
0xff078793 | addi x15 x15 -16 | addi a5,a5,-16 |
0x008787b3 | add x15 x15 x8 | add a5,a5,s0 |
0xfd47a783 | lw x15 -44(x15) | lw a5,-44(a5) |
0x00f70733 | add x14 x14 x15 | add a4,a4,a5 |
0xfec42783 | lw x15 -20(x8) | lw a5,-20(s0) |
0x00279793 | slli x15 x15 2 | slli a5,a5,2 |
0xff078793 | addi x15 x15 -16 | addi a5,a5,-16 |
0x008787b3 | add x15 x15 x8 | add a5,a5,s0 |
0xfce7aa23 | sw x14 -44(x15) | sw a4,-44(a5) |
0xfec42783 | lw x15 -20(x8) | lw a5,-20(s0) |
0x00178793 | addi x15 x15 1 | addi a5,a5,1 |
0xfef42623 | sw x15 -20(x8) | sw a5,-20(s0) |
0xfec42703 | lw x14 -20(x8) | lw a4,-20(s0) |
0x00900793 | addi x15 x0 9 | li a5,9 |
0xfae7d2e3 | bge x15 x14 -92 | ble a4,a5,.L3 |
0x00000793 | addi x15 x0 0 | li a5,0 |
0x00078513 | addi x10 x15 0 | mv a0,a5 |
0x03c12403 | lw x8 60(x2) | lw s0,60(sp) |
0x04010113 | addi x2 x2 64 | addi sp,sp,64 |
0x00008067 | jalr x0 x1 0 | jr ra |
运行结果及代码分析(运行平台:www.kvakil.me/venus/)
main:
addi sp,sp,-64 #sp=sp-64
sw s0,60(sp) #将s0中存放数据存入地址为[(sp+60)]的主存中
addi s0,sp,64 #s0=sp+64
li a5,10 #a5=10
sw a5,-20(s0) #将a5(#10)中存放数据存入地址为[(s0-60)]的主存中: 对应i=10
li a5,1 #a5=1
sw a5,-56(s0) #将a5(#1)中存放数据存入地址为[(s0-56)]的主存中
lw a5,-56(s0) #从主存地址为[(s0-56)]处读取数据写入a5
sw a5,-60(s0) #将a5中存放数据存入地址为[(s0-60)]的主存中
#以上三步对应Fibonacci[0]=Fibonacci[1]=1;
li a5,2 #a5=2
sw a5,-20(s0) #将a5(#2)中存放数据存入地址为[(s0-20)]的主存中: 对应i=2
j .L2
.L3:
lw a5,-20(s0) #从主存地址为[s0-20]处读取数据写入a5(i的值)
addi a5,a5,-1 #a5=a5+(-1)
slli a5,a5,2 #a5左移2位
addi a5,a5,-16 #a5=a5+(-16)
add a5,a5,s0 #a5=a5+s0
lw a4,-44(a5) #从主存地址为[(a5-44)]处读取数据写入a4
lw a5,-20(s0) #从主存地址为[(s0-20)]处读取数据写入a5
addi a5,a5,-2 #a5=a5+(-2)
slli a5,a5,2 #a5左移两位
addi a5,a5,-16 #a5=a5+(-16)
add a5,a5,s0 #a5=a5+s0
lw a5,-44(a5) #从主存地址为[(a5-44)]处读取数据写入a4
add a4,a4,a5 #a4=a4+a5
lw a5,-20(s0) #从主存地址为[(s0-20)]处读取数据写入a5
slli a5,a5,2 #a5左移两位
addi a5,a5,-16 #a5=a5+(-16)
add a5,a5,s0 #a5=a5+s0
sw a4,-44(a5) #将a4中存储的数据写入到主存地址[(a5-44)]处
#写入Fibonacci[i]
lw a5,-20(s0) #从主存地址为[(s0-20)]处读取数据写入a5
addi a5,a5,1 #a5++
sw a5,-20(s0) #将a5中存储的数据写入到主存地址[(s0-20)]处
#即s0-20暂存i值
.L2:
lw a4,-20(s0) #从主存地址为[(s0-20)]处读取数据写入a4
li a5,9 #将9存入a5
ble a4,a5,.L3 #if(a4 <= a5){goto L3;}
#以上两步为for循环结束条件,a4为i的数值,a5为结束条件,即i<=9,即i<10
li a5,0 #结束a5置0
mv a0,a5 ##结束a5置0
lw s0,60(sp)
addi sp,sp,64
jr ra
斐波那契数列存在内存0x7fffffb4——0x7fffffd8中
具体运行过程录制为视频“riscv编译运行斐波那契数列.mp4”存于压缩包中
Cross compile the Fibonacci.c for MIPS targets, or write the assembly code of Fibonacci.c in MIPS assembly language, and run it in Mars simulator. Observe the memory areas in bytes for the Fibonacci numbers given by the Mars simulator.
交叉编译平台:Compiler Explorer
交叉编译代码:
main:
addiu $sp,$sp,-64
sw $fp,60($sp)
move $fp,$sp
li $2,1 # 0x1
sw $2,16($fp)
lw $2,16($fp)
nop #空指令
sw $2,12($fp)
li $2,2 # 0x2
sw $2,8($fp)
b $L2
nop #空指令
$L3:
lw $2,8($fp)
nop
addiu $2,$2,-1
sll $2,$2,2
addiu $3,$fp,8
addu $2,$3,$2
lw $3,4($2)
lw $2,8($fp)
nop
addiu $2,$2,-2
sll $2,$2,2
addiu $4,$fp,8
addu $2,$4,$2
lw $2,4($2)
nop
addu $3,$3,$2
lw $2,8($fp)
nop
sll $2,$2,2
addiu $4,$fp,8
addu $2,$4,$2
sw $3,4($2)
lw $2,8($fp)
nop
addiu $2,$2,1
sw $2,8($fp)
$L2:
lw $2,8($fp)
nop
slti $2,$2,10
bne $2,$0,$L3
nop
move $2,$0
move $sp,$fp
lw $fp,60($sp)
addiu $sp,$sp,64
jr $31
nop
代码分析
mips整体与riscv结构类似,下图为两者交叉编译结果比较
显然,两者主要区别在于指令集中指令用法不同,但由于两者交叉编译均为32位且均为精简指令集,整体代码思路及结构均相似。通过该程序可以了解到以下代码区别:
Riscv | Mips | |
运算指令 | addi s0,sp,64 | addiu $sp,$sp,-64 |
数据传输指令 | sw a5,-20(s0) | sw $2,16($fp) |
移位运算 | slli a5,a5,2 | sll $2,$2,2 |
跳转指令 | J .L2 | b $L2 |
条件分支指令 | ble a4,a5,.L3 | bne $2,$0,$L3 |
总结:
Riscv的指令集中涉及寄存器的指令直接记以寄存器的名称,涉及Mips的指令集中以寄存器代码前加‘$’表示,sp、fp等栈指针前直接加‘$’。
运行结果见视频
write the assembly code of Fibonacci.c in MIPS assembly language, and run it in WinMIPS64 simulator. Observe the memory areas in bytes for the Fibonacci numbers given by the WinMIPS simulator.
参考文章:
WinMIPS64工具进行MIPS指令集实验(一)_SweeNeil的博客-CSDN博客
WinMIps64指令集实验_Ryan-S的博客-CSDN博客_winmips64
MIPS64寄存器与指令集_Wo_der的博客-CSDN博客_mips64 寄存器
WinMIPS64工具进行MIPS指令集实验(二)_SweeNeil的博客-CSDN博客
代码
.data
a: .word 1,1;存入初始数据
.text
;initialize registers
daddi r1,r0,a
daddi r4,r0,10;循环次数
Loop:
lw r5,0(r1)
lw r6,8(r1)
dadd r8,r5,r6 ; a[i]=a[i-1]+a[i-2]
sw r8,16(r1) ; store value in a[i]
daddi r1,r1,8 ; increment memory pointers
daddi r4,r4,-1 ; i--
bnez r4,Loop
end: halt
参考博客上进行a[i]=a[i]+b[i]+c[i]运算的程序,自己编写计算斐波那契数列前十位代码,与之前最大的不同的是winmips64须有程序段的声明,结束使用halt表示结束
该代码先存入初始的两个值1,1,然后利用我使用lw r5,0(r1),lw r6,8(r1)始终读取两个数据放入r5,r6中,再使r8=r5+r6,将r8存入r1偏移16位的地址。以上实现a[i]=a[i-1]+a[i-2]。而后再利用daddi r1,r1,8实现指针偏移;
daddi r4,r4,-1和bnez r4,Loop实现计数判断并循环
实验过程
Asm.exe检验编写结果
打开文件
运行结果
过程分析
该图为指令的流水线,红色部分为正在执行的指令,黄色为取指过程,蓝色为译码过程,绿色为访存过程,粉色为写回过程。
以上图为例,dadd r8,r5,r6指令正在执行,上一条执行的指令lw r6,8(r1)正在访存,再上一条指令lw r5,0(r1)正在写回数据,而与此对应的,register模块中R5寄存器高亮,表示正在写入。
再往下看,它的下一条指令sw r8,16(r1)正在译码,而daddi r1,r1,8,正在进行取指。
Conclusion
这次实验前前后后做了半个来月,磕磕绊绊看着网上的blog一边学一边操作,中间遇到过不少问题,也解决了不少问题,还是简单记录一下
第一个大门槛是.c文件交叉编译为riscv指令的.s文件,交叉编译的结果在venus网站上跑一直会显示伪指令不识别,最后咨询老师了解到这个网站支持的是32位的格式,于是重新编译为32位的riscv指令,最终成功运行。
其次便是与riscv和mips之间的区别相比,和x86的指令之间格式差距巨大,参考指令集手册,才勉强弄懂了指令含义。
不过感觉其实是越往后越简单,精简指令集大多都是相通的,只有指令格式在细节略有区别,做到winMIPS64时交叉编译的无法正确运行,我也就干脆放弃了交叉编译的方法(后来才看到是让自己写,还好还好),在网上简单查了几篇blog,弄懂了指令的格式,没费多长时间就完成了最后一个实验。