数字逻辑与处理器基础 MIPS汇编编程实验
数字逻辑与处理器基础
MIPS汇编编程实验
实验指导书
2019-2020年 春季学期
目录
MARS环境安装与基础使用方法
实验内容一:基础练习
系统调用
循环分支
数组指针
函数调用
实验内容二:综合练习
冒泡排序
快速排序
归并排序
参考资料
安装JRE
运行JAVA程序包需要运行环境:Java Runtime Environment (JRE)
Windows系统运行 JavaSetup8u241.exe按提示进行安装。
如果安装中又遇到问题,或者其他操作系统可以访问JAVA官网:https://www.java.com/zh_CN/,下载完成后按提示进行安装。
运行MARS
如果JRE正确安装,双击Mars4_5.jar即可打开MARS仿真器。
如果打不开,先检查JRE是否安装正确,可以考虑重新安装。
如果是软件包的问题可以进入MARS官网下载。 https://courses.missouristate.edu/KenVollmar/mars/download.htm
接下来将用example_0.asm作为例子演示MARS的用法。
运行MARS
寄存器列表
指令运行速度
汇编执行调试功能
基本编辑功能
文件读写功能
主要编辑区
输出信息区
运行MARS后的主要界面如图所示。
主要编辑区用于编写汇编指令。
输出信息区可以查看程序运行过程中的输出和系统报错等。
寄存器列表实时显示当前运行状态下各个寄存器存储的值。
见 example_0.asm
数据声明,此部分数据存在0x10010000
打开读取文件,并将数据写入in_buff
打开文件并将out_buff的数据写入
初始化变量
循环体
跳转条件
example_0.asm 内包含一个从文件读取数据并写入另一个文件的例子
6
汇编运行
首先打开汇编文件example_0.asm
点击汇编按钮即可切换到执行页面,源代码汇编成基础指令和机器码,PC置为0x00400000,并等待执行。
执行页面内可以看到汇编后的基础指令和对应的机器码,每条指令的指令地址。
断点
指令
地址
机器码
基础指令
源代码
Memory查看器
可以选择查看哪一段地址
汇编运行
源代码:用户编写的汇编代码,包括标记,伪代码等。
基础指令:汇编后的指令,伪代码被转换,标记被翻译。
地址&机器码:与基础指令一一对应,32bit一条指令,地址依次加四。
断点:调试用,当执行到这一句时暂停。
例子:
伪代码 la $a0 input_file
Input_file 被翻译为真正的地址0x10010400
la 伪指令被转换为 lui+ori真指令
lui $1 0x1001为 {fhex,0hex,1hex,1001hex}=0x3c011001
ori $4 $1 0x0400为{dhex,1hex,4hex,0400hex}=0x34240400
汇编运行
执行:从第一条指令开始连续执行直到结束。
单步执行:执行当前指令并跳转到下一条。
单步后退:后退到最后一条指令执行前的状态(包括寄存器和memory)
暂停&停止:在连续执行的时候可以停下来,一般配合较慢的指令运行速度,不用于调试。调试最好使用断点功能。
重置:重置所有寄存器和memory。
汇编 执行 单步执行 单步后退 暂停 停止 重置
黄色指令代表当前指令
是即将执行但尚未执行的指令
也是pc寄存器对应的指令
指令运行速度
汇编运行
点击执行按钮后,所有指令执行完毕。
可以看到各个寄存器内的值发生了变化。
Memory中in_buffer, out_buffer地址对应的数据发生变化。
输出区正确打印了对应的数据并提示,程序执行完地址最大的指令并且没有后续指令了( drop off bottom )
在out_buffer的位置依次存放了 0x180,0x377,0x30a,0x394
在in_buffer的位置依次存放了 0x4,0x180,0x377,0x30a,0x394
输出区打印了384,887,778,916,并显示drop off bottom
汇编运行
在38行的指令处设置断点。并点击运行按钮两次,程序停在该位置。
可以看到程序向out_buffer中写入两个数,也向输出区打了两个数,各个寄存器也停留在对应状态。
在out_buffer的位置依次存放了0x180,0x377
在打印某个整数和打印逗号之间的指令设置断点
输出区打印了384,887
在in_buffer的位置依次存放了 0x4,0x180,0x377,0x30a,0x394
PC取值为0x00400070
汇编基本结构
数据段
以“.data”记号开头。
包括常量数据和固定数组的声明。
代码段
以“.text”记号开头。
包括待执行的代码和行标记。
注释
“#”为注释标记。
可以出现在任意位置。
“#”及其之后的所有内容均被忽略。
见 example_1.asm
汇编基本结构
.align x
将下一个数据项对齐到特定的byte边界。如果要读取的数据是以2byte,4byte,8byte为单位的,需要对齐到对应的边界上。x取值0表示1byte,1表示2byte,2表示4byte,3表示8byte。
.ascii, .asciiz
表示字符串,其中asciiz会自动在最后补上null字符。
.byte, .half, .word
表示数组常量按1byte,2byte,4byte存储
.space
表示一个以byte计长度的数组
见 example_2.asm
代码段基本结构
代码段一般由若干段顺序排列的指令序列构成。从第一条指令开始执行。每执行完一条指令后会顺序执行下一条指令,除非发生跳转
label_name : add $0 $1 $2
字符串+冒号代表标记,可以用在对应指令同一行开头或者对应指令上一行。
一个函数一般以函数名为第一条指令的label(函数入口)。程序内一般包括入栈,程序主体,设置返回值,出栈,返回上一级程序。
见 example_3.asm
主过程
代码段基本结构
代码段一般由若干段顺序排列的指令序列构成。从第一条指令开始执行。每执行完一条指令后会顺序执行下一条指令,除非发生跳转
label_name : add $0 $1 $2
字符串+冒号代表标记,可以用在对应指令同一行开头或者对应指令上一行。
一个函数一般以函数名为第一条指令的label(函数入口)。程序内一般包括入栈,程序主体,设置返回值,出栈,返回上一级程序。
子过程,接上页
见 example_3.asm
实验内容1
用MIPS32汇编指令完成下列任务,调试代码并获得正确的结果。
练习1-1:系统调用。
练习1-2:循环,分支。
练习1-3:数组,指针。
练习1-4:函数调用。
练习1-1:系统调用
练习使用MARS模拟器中的系统调用syscall,使用syscall可以完成包括文件读写,命令行读写(标准输入输出),申请内存(malloc)等辅助功能。
系统调用基本的使用方法是
向$a*寄存中写入需要的参数(如果有)
向$v0寄存器中写入需要调用的syscall的编号
使用”syscall”指令进行调用
从$v0中读取调用的返回值(如果有)
更多具体的使用方法可以参照MARS模拟器的Help中的相关内容。
练习1-1:系统调用
用MIPS汇编指令实现exp1_1_syscall.cpp 的功能并提交汇编代码,尽量在代码中添加注释。
exp1_1_syscall.cpp代码内容主要包括:
申请一个4byte整数的内存空间。
从”a.in”读取一个整数。
从键盘输入一个整数。
对上面两个整数求和。
向屏幕打印求和结果。
向”a.out”写入求和结果。
#include “stdio.h”
int main()
{
FILE * infile ,*outfile;
int i;
int* buffer;
buffer = new int[1];
infile = fopen(“a.in”,”rb”);
fread( buffer, 4, 1, infile );
fclose(infile);
scanf(“%d”,&i);
buffer[0] = buffer[0]+i;
printf(“%d”,buffer[0]);
outfile = fopen(“a.out”,”wb”);
fwrite( buffer, 4, 1, outfile);
fclose(outfile);
return 0;
}
exp1_1_syscall.cpp文件内容
练习1-2:循环分支
1、用汇编代码给出if-then-else的一般性结构。(分支条件可以用$s0==0代替)
2、用汇编代码给出while循环,do-while循环,while中continue,while中break的一般性结构。(循环条件可以用$s0==0代替)
3、用MIPS语言实现exp1_2_loop.cpp中的功能并提交汇编代码,尽量在代码中添加注释。
练习1-3:数组、指针
现有右边代码作为条件,求解下列表达式的值并用32位十六进制数表示。
(1) A[0]=? (2)A[1]=?
(3) p_A[0]=? (4) p_A[1]=?
(5) (int)p_A=? (6) (int)(p_A+1)=?
(7) *(int*)((int)p_A+4)=?
用MIPS汇编指令实现exp1_3_array.cpp 的功能并提交汇编代码,尽量在代码中添加注释。
int A [2]; // &A=0x10010000
A[0] = 0x00000012;
A[1] = 0x00000021;
int* p_A = &A;
练习1-4:函数调用
调用函数的流程为:
设置参数寄存器$a0~$a3。
使用jal跳转到被调函数。
使用被调函数的返回值$v0~$v1执行接下来的内容。
寄存器编号 助记符 用法
0 zero 永远为0
1 at 用做汇编器的临时变量
2-3 v0, v1 用于过程调用时返回结果
4-7 a0-a3 用于过程调用时传递参数
8-15 t0-t7 临时寄存器。在过程调用中被调用者不需要保存与恢复
24-25 t8-t9
16-23 s0-s7 保存寄存器。在过程调用中被调用者一旦使用这些寄存器时,必须负责保存和恢复这些寄存器的原值
26,27 k0,k1 通常被中断或异常处理程序使用,用来保存一些系统参数
28 gp 全局指针。一些运行系统维护这个指针来更方便的存取static和extern变量
29 sp 堆栈指针
30 fp 帧指针
31 ra 返回地址
练习1-4:函数调用
被调函数的流程为:
分配栈空间($sp-4*n)。
将需要保存的寄存器存入栈。
使用输入参数$a0~$a3执行函数内容并将结果存入返回寄存器$v0~$v1。
将入栈的数据恢复到寄存器。
j $ra 返回上级函数。
寄存器编号 助记符 用法
0 zero 永远为0
1 at 用做汇编器的临时变量
2-3 v0, v1 用于过程调用时返回结果
4-7 a0-a3 用于过程调用时传递参数
8-15 t0-t7 临时寄存器。在过程调用中被调用者不需要保存与恢复
24-25 t8-t9
16-23 s0-s7 保存寄存器。在过程调用中被调用者一旦使用这些寄存器时,必须负责保存和恢复这些寄存器的原值
26,27 k0,k1 通常被中断或异常处理程序使用,用来保存一些系统参数
28 gp 全局指针。一些运行系统维护这个指针来更方便的存取static和extern变量
29 sp 堆栈指针
30 fp 帧指针
31 ra 返回地址
int sum( int a){
b=a;
temp0 = 0
if (b!=0){
temp1 = b-1;
temp0 = sum(temp1);
}
b=b+temp0;
return b;
}
sum(3)
=3+sum(2)
=3+2+sum(1)
=3+2+1+sum(0)
=3+2+1+0
四次函数调用
Main ->sum(3),
sum(3) ->sum(2),
sum(2) ->sum(1),
sum(1) ->sum(0),
逐步完成函数调用的编译
1、先编译其他语句。
2、拆解编译调用函数的语句
3、在首尾分别补上入栈和出栈
sum:#保证$a0已经被赋予正确的值
(保护现场)
addi $s0 $a0 0
addi $t0 $0 0
beq $s0 $zero skip
addi $t1 $s0 -1
(调用sum($t1)并将返回值给$t0)
skip:
add $s0 $s0 $t0
addi $v0 $s0 0
(恢复现场)
jr $ra#执行到这里可以直接跳回去
int sum( int a){
b=a;
temp0 = 0
if (b!=0){
temp1 = b-1;
temp0 = sum(temp1);
}
b=b+temp0;
return b;
}
练习1-4:函数调用
1、继续完成Sum过程的编译,使得其可以完成计算的任务。(在实验报告中完成即可)
实验内容2
用MIPS32汇编指令将以下C语言代码转化为汇编语言执行,调试代码并获得正确的结果。
冒泡排序
熟悉基本操作,练习文件读取写入。文件名” bsort.asm”
快速排序
练习递归函数调用,入栈出栈等操作。文件名” qsort.asm”
归并排序
练习指针,数组链表相关的操作。文件名” msort.asm”
提交汇编代码,并在实验报告中记录一些调试过程出现的问题,debug心得等。
冒泡排序-C代码
#include “stdio.h”
int swap(int v[], int k)
{
int temp;
temp = v[k];
v[k] = v[k+1];
v[k+1] = temp;
return 0;
}
int bubsort (int v[], int n)
{
int i, j;
for(i=0; i
swap(v,j);
}
}
return 0;
}
int main()
{
FILE * infile ,*outfile;
int buffer [1001];
infile = fopen(“a.in”,”rb”);
fread ( buffer, 4, 1001, infile );
fclose(infile );
int N = buffer[0];
bubsort(&(buffer[1]),N);
outfile = fopen(“a.out”,”wb”);
fwrite( &(buffer[1]), 4, N, outfile);
fclose(outfile);
return 0;
}
快速排序-C代码
#include “stdio.h”
int quickSort(int * arr,int left , int right){
int i = left , j = right;
int key = arr[left];
while(1){
while(arr[j] >= key && i
p_left = (int *)p_left[1];
}while(1);
// 如果到达左链尾端 , 右链直接接上
if(p_left[1] == (int)NULL ){
p_left[1] = (int)p_right;
break;
}
int * p_right_temp = p_right;
do{// 寻找右链待插入片段
if(p_right_temp[1] == (int)NULL) break;
if(((int *)p_right_temp[1])[0]>((int *)p_left[1])[0])
break;
p_right_temp = (int * )p_right_temp[1];
}while(1);
// 完成插入操作
int * temp_right_pointer_next = (int *)p_right_temp[1];
p_right_temp[1] = p_left[1];
p_left[1] = (int) p_right;
p_left = p_right_temp;
p_right = temp_right_pointer_next;
if(p_right==NULL) break;
}while(1);
int rv = head[1];
delete head;
return (int * ) rv;
}
接下页
接上页
// 归并排序主函数 , 先找链表中点 , 再分别排序 , 最后归并
int * msort(int * head){
if(head[1]== (int)NULL) return head;
int * stride_2_pointer = head;
int * stride_1_pointer = head;
do{// 通过同时进行步长为 1 和步长为 2 的跳转找中点
if(stride_2_pointer[1] == (int)NULL) break;
stride_2_pointer = (int *) stride_2_pointer [1];
if(stride_2_pointer[1] == (int)NULL) break;
stride_2_pointer = (int *) stride_2_pointer [1];
stride_1_pointer = (int *) stride_1_pointer [1];
}while(1);
// 拆成两个链表分别排序 , 再归并 。
stride_2_pointer = (int *)stride_1_pointer [1];
stride_1_pointer[1] = (int)NULL;
int * l_head = msort(head);
int * r_head = msort(stride_2_pointer);
return merge(l_head, r_head);
}
int main(){
FILE * infile ,*outfile;
int buffer [1001];
infile = fopen(“a.in”,”rb”);
fread ( buffer, 4, 1001, infile );
fclose(infile );
int N = buffer[0];
int * head = new int[2];
head[1] = (int)NULL;
int * pointer=head;
for(int idx =1; idx<=N;idx++){
pointer[1]=(int) new int [2];
pointer=(int *) pointer[1];
pointer[0] = buffer[idx];
pointer[1] = (int)NULL;
}
head[1] =(int) msort((int *)head[1]);
pointer=head;
outfile = fopen("a.out","wb");
do{
pointer =(int *)pointer[1];
if(pointer==NULL) break;
fwrite( pointer, 4, 1, outfile);
}while(1);
fclose(outfile);
while(head!=NULL){
int * temp = head;
head = (int *)head[1];
delete temp;
}
return 0;
}
作业提交要求
作业用一个压缩包提交,压缩包名称:”学号_姓名.7z”。推荐用7z格式,其他常见压缩格式也可以。
压缩包打开后需要包含:
一个“实验报告.pdf”文件,
一个”exp_1_1.asm”,一个”exp_1_2.asm”,一个”exp_1_3.asm”
一个”exp_2_1.asm”,一个”exp_2_2.asm”,一个”exp_2_3.asm”
三个排序算法都需要读入文件“a.in”,然后写入“a.out”。
输入文件’a.in’内容为 4 × (N + 1) 个 bytes,其中前四个 bytes 为正整数 N(N<=1000),接下来 4N 个 bytes 分别为 N 个正整数(小于 2^16)。
输出文件’a.out’内容为 4 × N 个 bytes,为排序后的 N 个正整数
参考资料
MIPS32 官方网站资料 https://www.mips.com/products/architectures/mips32-2/
指令集架构简介 Introduction to the MIPS32 Architecture.pdf
指令集手册 MIPS32 Instruction Set Manual.pdf
附件
JAVA环境安装包 JavaSetup8u241.exe
MARS模拟器 Mars4_5.jar
二进制文件查看器 pxBinaryViewerSetup.exe
随机数生成
/docProps/thumbnail.jpeg