本人菜鸡一个,在csdn上各位大佬的blog 指点下磕磕绊绊完成了computer architecture的实验,前后历时半个来月,无以为报,献上自己的实验报告,由于我大二下上的网课一直在摸鱼...许多知识点掌握的不是很牢,其中可能会有些错误,也有很多不详细的地方,欢迎大佬指正,也希望能对后人有点帮助,毕竟我一开始做这玩意毫无头绪浪费了不少时间
由于没有输出函数,生成.EXE时临时加入system(”pause”)以证明运行成功
汇编完整代码如下:
.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
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
观察可知,代码运行中寄存器状态变化符合上述对汇编代码观察得到的结论
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 |
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”存于压缩包中
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等栈指针前直接加‘$’。
运行结果见视频
参考文章:
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,正在进行取指。
这次实验前前后后做了半个来月,磕磕绊绊看着网上的blog一边学一边操作,中间遇到过不少问题,也解决了不少问题,还是简单记录一下
第一个大门槛是.c文件交叉编译为riscv指令的.s文件,交叉编译的结果在venus网站上跑一直会显示伪指令不识别,最后咨询老师了解到这个网站支持的是32位的格式,于是重新编译为32位的riscv指令,最终成功运行。
其次便是与riscv和mips之间的区别相比,和x86的指令之间格式差距巨大,参考指令集手册,才勉强弄懂了指令含义。
不过感觉其实是越往后越简单,精简指令集大多都是相通的,只有指令格式在细节略有区别,做到winMIPS64时交叉编译的无法正确运行,我也就干脆放弃了交叉编译的方法(后来才看到是让自己写,还好还好),在网上简单查了几篇blog,弄懂了指令的格式,没费多长时间就完成了最后一个实验。