《模拟器与汇编程序设计论文.doc》由会员分享,可在线阅读,更多相关《模拟器与汇编程序设计论文.doc(53页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、华中科技大学计算机科学与技术学院 C语言课程设计报告华中科技大学计算机科学与技术学院C语言课程设计报告 题目: 模拟器与汇编程序设计目 录一、系统需求分析1二、总体设计6三、数据结构设计8四、详细设计10五、系统实现14六、运行测试与结果分析47七、总结49八、参考文献50九、指导教师评语51II华中科技大学计算机科学与技术学院 C语言课程设计报告一、 系统需求分析本系统根据给出的指令集架构ISA编程实现一个模拟器和汇编程序,能够使用汇编程序将由给定指令集和指令格式编写的汇编源程序翻译成目标程序,并能够在模拟器上运行汇编后的目标程序,得到正确结果。系统还包含两个汇编源程序,供汇编程序翻译成目标
2、代码,即机器码。汇编源程序1的功能是求出1+2+3+100,并且正确输出运算结果。汇编源程序2的功能是将字符串“Simulator and Assembler”拷贝到另一个字符串中,并且输出另一个字符串,源程序可以自动判断字符串尾部并结束拷贝以及结束输出而不用考虑字符串长度的设定。具体内容如下:1. 用C语言编制汇编程序,将此简单计算机的汇编源程序翻译成目标代码,即机器码。为了测试所编制汇编程序的正确性,需用以上介绍的指令集编写两个汇编源程序,汇编源程序的功能要求为: 求1+2+3+100,并输出运算结果。 求将“Simulator and Assembler”拷贝复制到新串并输出运算结果。串
3、并输出运算结果。其中,32条指令以及伪指令和它们的功能如下:(1) 停机指令:HLT功能:终止程序运行。(2) 无条件转移指令:JMP label功能:将控制转移至标号label处,执行标号label后的指令。(3) 比较运算转移指令:CJMP label功能:如果程序状态字中比较标志位c的值为1(即关系运算的结果为真),则将控制转移至标号label处,执行标号label后的指令;否则,顺序往下执行。(4) 溢出转移指令:OJMP功能:如果程序状态字中比较标志位o的值为1(即算术运算的结果发生溢出),则将控制转移至标号label处,执行标号label后的指令;否则,顺序往下执行。(5) 调用子
4、程序指令:CALL label功能:将通用寄存器AG、程序状态字PSW、程序计数器PC中的值保存到ES,然后调用以标号label开始的子程序,将控制转移至标号label处,执行标号label后的指令。(6) 子程序返回指令:RET功能:将ES中保存的通用寄存器AZ、程序状态字PSW和程序字数器PC的值恢复,控制转移到子程序被调用的地方,执行调用指令的下一条指令。(7) 入栈指令:PUSH reg0功能:将通用寄存器reg0的值压入堆栈SS,reg0可以是AG和Z八个通用寄存器之一。(8) 出栈指令:POP reg0功能:从堆栈SS中将数据出栈到寄存器reg0,reg0可以是AG七个通用寄存器之
5、一,但不能是通用寄存器Z。(9) 取字节数据指令:LOADB reg0 symbol功能:从字节数据或字节数据块symbol中取一个字节的数据存入寄存器reg0,所取的字节数据在数据块symbol中的位置由寄存器G的值决定。用C的语法可将此指令的功能描述为: reg0 = symbolG例如,假设用伪指令定义了以下字节数据块num: BYTE num10 = 5,3,2,8,6,9,1,7,4,0如果要将字节数据块num中第5个单元的值(即下标为4的元素)取到寄存器C,指令如下: LOADI G 5 LOADB C num后面的指令LOADW、STOREB和STOREW在操作上与此指令类似。(
6、10) 取双字节数据指令:LOADW reg0 symbol功能:从双字节数据或双字节数据块symbol中取一个双字节的数据存入寄存器reg0,所取的双字节数据在数据块symbol中的位置由寄存器G的值决定。(11) 存字节数据指令:STOREB reg0 symbol功能:将寄存器reg0的值存入字节数据或字节数据块symbol中的某个单元,存入单元的位置由寄存器G的值决定。用C的语法可将此指令的功能描述为: symbolG = reg0(12) 存双字节数据指令:STOREW reg0 symbol功能:将寄存器reg0的值存入双字节数据或双字节数据块symbol中的某个单元,存入单元的位
7、置由寄存器G的值决定。(13) 取立即数指令:LOADI reg0 immediate功能:将指令中的立即数immediate存入寄存器reg0。立即数被当作16位有符号数,超出16位的高位部分被截掉。例如: LOADI B 65535寄存器B的值为-1。 LOADI B 65537寄存器B的值为1。(14) 空操作指令:NOP功能:不执行任何操作,但耗用一个指令执行周期。(15) 控制台输入指令:IN reg0 0功能:从输入端口(即键盘输入缓冲区)取一个字符数据,存入寄存器reg0。(16) 控制台输出指令:OUT reg0 15功能:将寄存器reg0的低字节作为字符数据输出到输出端口(即
8、显示器)。(17) 加运算指令:ADD reg0 reg1 reg2功能:将寄存器reg1的值加上reg2的值,结果存入寄存器reg0。如果结果超过16位有符号数的表示范围,将发生溢出,使程序状态字的溢出标志位o置为1;如果未发生溢出,则使程序状态字的溢出标志位o置为0。(18) 加立即数指令:ADDI reg0 immediate功能:将寄存器reg0的值加上立即数immediate,结果仍存入寄存器reg0。如果结果超过16位有符号数的表示范围,将发生溢出,使程序状态字的溢出标志位o置为1;如果未发生溢出,则使程序状态字的溢出标志位o置为0。(19) 减运算指令:SUB reg0 reg1
9、 reg2功能:将寄存器reg1的值减去reg2的值,结果存入寄存器reg0。如果结果超过16位有符号数的表示范围,将发生溢出,使程序状态字的溢出标志位o置为1;如果未发生溢出,则使程序状态字的溢出标志位o置为0。(20) 减立即数指令:SUBI reg0 immediate功能:将寄存器reg0的值减去立即数immediate,结果仍存入寄存器reg0。如果结果超过16位有符号数的表示范围,将发生溢出,使程序状态字的溢出标志位o置为1;如果未发生溢出,则使程序状态字的溢出标志位o置为0。(21) 乘运算指令:MUL reg0 reg1 reg2功能:将寄存器reg1的值乘以reg2的值,结果
10、存入寄存器reg0。如果结果超过16位有符号数的表示范围,将发生溢出,使程序状态字的溢出标志位o置为1;如果未发生溢出,则使程序状态字的溢出标志位o置为0。(22) 除运算指令:DIV reg0 reg1 reg2功能:将寄存器reg1的值除以reg2的值,结果存入寄存器reg0,这里进行的是整数除运算。如果寄存器reg2的值为零,将发生除零错。(23) 按位与运算指令:AND reg0 reg1 reg2功能:将寄存器reg1的值与reg2的值进行按位与运算,结果存入寄存器reg0。(24) 按位或运算指令:OR reg0 reg1 reg2功能:将寄存器reg1的值与reg2的值进行按位或
11、运算,结果存入寄存器reg0。(25) 按位异或运算指令:NOR reg0 reg1 reg2功能:将寄存器reg1的值与reg2的值进行按位异或(按位加)运算,结果存入寄存器reg0。(26) 按位取反运算指令:NOTB reg0 reg1功能:将寄存器reg1的值按位取反后,结果存入寄存器reg0。(27) 算术左移运算指令:SAL reg0 reg1 reg2功能:将寄存器reg1的值算术左移reg2位,结果存入寄存器reg0。在进行算术左移时,低位空位用0填充。(28) 算术右移运算指令:SAR reg0 reg1 reg2功能:将寄存器reg1的值算术右移reg2位,结果存入寄存器r
12、eg0。在进行算术右移时,高位空位用符号位填充。(29) 相等关系运算指令:EQU reg0 reg1功能:将两个寄存器reg0和reg1的值进行相等比较关系运算:reg0 = reg1,关系运算的结果为逻辑真或逻辑假,存入程序状态字中的比较标志位c。(30) 小于关系运算指令:LT reg0 reg1功能:将两个寄存器reg0和reg1的值进行小于关系运算:reg0 reg1,关系运算的结果为逻辑真或逻辑假,存入程序状态字中的比较标志位c。(31) 小于等于关系运算指令:LTE reg0 reg1功能:将两个寄存器reg0和reg1的值进行小于等于关系运算:reg0 = reg1,关系运算的
13、结果为逻辑真或逻辑假,存入程序状态字中的比较标志位c。(32) 比较标志位取反指令:NOTC功能:将程序状态字中的比较标志位c求反,即将逻辑真变为逻辑假,将逻辑假变为逻辑真。(33) 字节数据定义伪指令:BYTE symboln = . 蓝色字体部分为可选项 或:BYTE symboln = . 蓝色字体部分为可选项功能:定义长度为1字节的字节型数据或数据块,字节型数据块类似于C的字符数组。(34) 字数据定义伪指令:WORD symboln = . 蓝色字体部分为可选项功能:定义长度为2字节的双字节型数据或数据块,双字节型数据块类似于C的整型数组(16位系统)。2. 用C语言编制一个模拟器,
14、能够模拟此简单计算机执行汇编程序生成的目标代码,得到运行结果。注:编写好编译程序和模拟器后,再用八皇后程序检验。二、总体设计本系统分为3个部分:汇编程序,模拟器,汇编源程序。系统通过汇编程序将汇编源程序翻译成目标程序,即目标代码,再在模拟器上运行该目标程序,从而得到正确的结果。首先对于第一部分汇编程序。该程序分3步将汇编源程序翻译成目标代码。第一:整体扫描一次汇编源程序,去掉汇编源代码里面的空行和注释,并且对于伪指令,建立2个链表分别用来存放标号以及变量。其中标号链表里要存放标号的名称以及所在行数(偏移量即行数*4,行号从0开始),而变量的链表则需要存放变量名、变量的个数(即对于变量数组的话数
15、组下标值为多少)、变量的偏移量、变量的值以及变量所占字节数(用来判断变量为BYTE类型还是WORD类型)。第二:再次扫描汇编源程序,将可执行指令翻译成目标代码,并存入目标文件。第三:将伪指令所定义的变量的值放在所有目标代码的后面,变量没有初值也需分配存储空间,以四个字节为单位划分,不足四字节时用0补齐,最后再输出变量所占的字节数,用于模拟器中寻找SS段的初始地址。汇编程序里感觉最难的还是对变量的赋值,要分引号赋值情况和花括号赋值情况:前者需取出引号内的字符串,在逐个字符的赋值;后者则需要将每个逗号前的字符串取出变成数字在进行赋值,直到遇到反花括号为止。第二部分模拟器。先向系统申请动态存储区,模
16、拟内存,然后定位好CS、DS、SS、ES的初始地址,其中CS段的地址为模拟内存的初始地址,从文件中取出目标胆码,加载到模拟内存,指令计数器也随着改变,此时SS段的地址则是PC的地址,在通过上面所述的变量总共占的字节数,计算出DS此时的地址,ES的地址则是模拟内存的末端减去一个ES的长度(ES的长度为变量结构的长度)。接下来就可以让PC重新指向MEM顶端的第一条指令,然后模拟处理器去执行命令,最后一次正确写出32个函数和两个伪指令的解码方法。个人认为模拟器的难点在于CS、DS、SS、ES的地址的定位以及CALL函数和RET函数的处理。第三部分汇编源代码。这一部分需要我们熟悉32个函数以及2个伪指
17、令的功能。我的方法是先用C写出两个程序的处理代码,再思考如何用汇编语言的指令助记符去执行这个C程序,经过多次的调试,便可得到正确的汇编源代码。此处难点在于对字符串处理的时候如何灵活使用助记符来准确表达我们要实现的功能。三、数据结构设计对于汇编程序伪指令部分的标号建立的标号的链表:采用先进先出方法构建标号链表,链表表头仅作定位使用,不进行赋值,在链表的第2个结点才开始为第1个标号进行赋值,之后的结点正常赋值。如下图所示:标号2标号1表头NULL标号n其中,每个标号的内容情况如下图所示(偏移量=所在行数*4):标号名MING名标号所在行数 对于编译程序里伪指令部分的变量建立的变量的链表,该链表将W
18、ORD型变量和BYTE型变量处理在同一链表内。该链表同样采用先进先出方法构建变量链表,链表表头仅起定位使用,不进行赋值,在链表的第2个结点才开始为第1个变量进行赋值,之后的结点正常赋值。如下图所示:变量2变量1表头NULL变量n其中:每个变量结点所包含的内容如下图所示:变量名值变量个数偏移量字节数变量上图中变量个数即当伪指令定义一个变量数组时用来统计数组下标大小的,若为单一变量则变量个数值为1。值即为变量的赋值。字节数用来判断WORD型变量和BYTE型变量,字节数为1则变量为BYTE型,字节数为2则变量为WORD型。四、详细设计编译程序:用fgets函数从源文件读入一行字符串,存入字符数组a_
19、line,用strchr函数对#号进行匹配,将其转换成0从而去掉注释,遇到空行时跳过并取下一行字符串。然后对a_line取指令助记符,如果对命令行匹配出:则说明有标号,对标号信息(包括标号名和所在行数)保存在标号链表内。如果该助记符匹配为“WORD”或者“BYTE”,则该行为伪指令,则取得该行第一个字符串为变量名,并用赋值函数对其变量名,偏移量,字节数,变量个数以及值分别进行赋值,对于值的赋值则考虑花括号赋值和引号赋值两种方式,并逐步处理保存在变量链表内。然后开始第2次读源代码,用相同的方法跳过空行,遇到伪指令的话也跳过不管,只处理那32个指令助记符。用sscanf取得助记符,找出对应的指令操
20、作码以及指令格式,然后通过对应的格式将源代码翻译成目标代码并保存。最后遍历变量链表,将变量的值以十六进制输出在目标文件中。编译程序对应的算法流程图如下:1源文件是否结束打开源文件开始5Y读入一行字符串N2处理空行和注释525是否为标号保存标号信息YN保存变量信息是否为变量Y5N源文件是否结束关闭源文件重新打开1行数+1结束关闭源文件和目标文件输出变量值YN空行或伪指令?读入一行代码跳过空行和伪指令YN去掉标号标号?YN翻译命令行并输出得助记符算操作码 模拟器:先向系统申请动态存储区,模拟内存。由于每条指令的长度是4个字节,所以将程序计数器PC定义成无符号长整型指针。这样,程序计数器的值加1就可
21、以指向下一条指令。且此时把MEM的地址赋给PC。CS的地址也和MEM的地址值相同。然后从文件中取出目标代码,加载到模拟内存,在这结束之后就可以将PC的地址赋值给SS,而ES的地址则是MEM的末端减去一个ES型结点的长度。再通过上面所述的变量总共占的字节数,计算出DS的地址。这样就确定了各个段的地址。接下来PC的地址重新变为MEM的地址值,开始取命令行,将PC指示的指令加载到指令寄存器IR后,PC指向下一条执行指令,然后就可以开始解码并执行指令。直到命令行全部读完,则free掉MEM,结束程序。对于解码程序,只需要将对应函数的功能完成即可,此只须注意PC的值的左右移动以及各个寄存器的值的保存和取
22、出。模拟器对应的算法流程图如下:开始对ES DS SS地址进行赋值打开文件文件到结尾?关闭文件YNPC = MEM取出代码1加载到内存PC+;1执行对应函数函数返回1结束NYPC+五、系统实现编译程序源代码:#include #include #include #include #define INS_COUNT (sizeof(g_ins_name) / sizeof(g_ins_name0)#define INS_SYM HLT, JMP, CJMP, OJMP, CALL, RET, PUSH, POP, LOADB, LOADW, STOREB, STOREW, LOADI, NOP,
23、 IN, OUT, ADD, ADDI, SUB, SUBI, MUL, DIV, AND, OR, NOR, NOTB, SAL, SAR, EQU, LT, LTE, NOTCconst char *g_ins_name = INS_SYM; /*定义存放指令助记符的数组*/*8个数字代表八种存放指令的格式*/const char ins_format33 = 12222133444451667575777778778881;typedef struct _BIAOHAO_INFO /*用于存储标号信息的链表*/ char name30; int line_num; struct _BIA
24、OHAO_INFO *next;biaohao;typedef struct _VAR /*用于存储变量信息的链表*/ char name30; int size; /*存储变量的个数*/ int pianyi; /*偏移量*/ short value256; int zijie; /*用于判断变量的类型*/ struct _VAR *next;var;int getinscode(const char *op_sym); /*由助记符得指令代码*/unsigned long transtocode(char *instr_line, int instr_num, biaohao *hd1,
25、var *hd2);/*指令的译码*/int getregnum(char *instr_line, char *reg_name);/*由寄存器名对应到编码*/int save_biaohao(char *biaohao_name, int line_num, biaohao *hd);/*保存标号的信息在链表里*/int save_var(char *a_line, int pianyi, int size, var *hd, int zijie);/*保存标号的信息在链表里*/long write_var(FILE *pfout, var *hd);/*最后输出变量的值在目标文件里*/i
26、nt main(int argc, char *argv) char a_line200; char op_sym8; int op_num = 0, i; char *pcpos; int zijie; FILE *pfin, *pfout; /*建立标号链表和变量的链表头指针,但头指针没放数据*/ biaohao *hd_biaohao = (biaohao *)malloc(sizeof(biaohao); hd_biaohao-next = NULL; var *hd_var = (var *)malloc(sizeof(var); hd_var-next = NULL; char b
27、iaohao_temp20; /*存放零时的标号名称*/ char string200; /*存放定义变量name和size的字符串*/ char var_size_str20; /*存放变量的大小(字符串格式)*/ int var_size; /*存放变量的大小(int型格式)*/ int pianyi_num = 0; int n; int line_num = 0; /*存代码当前的行数*/ if(argc3) /*检查命令行参数数目*/ printf(ERROR: no enough command line argumentsn); return 0; if(pfin = fopen
28、(argv1, r) = NULL)/*打开源代码文件*/ printf(ERROR: cannot open file %s for reading!n, argv1); return 0; if(pfout = fopen(argv2, w) = NULL)/*打开目标代码文件*/ printf(ERROR: cannot open file %s for writing!n, argv2); return 0; while(!feof(pfin) fgets(a_line, 200, pfin); /*从源文件取一行命令*/ if(pcpos = strchr(a_line, #) !=
29、 NULL) *pcpos = 0; /*去掉注释*/ n = sscanf(a_line, %s, op_sym); /*取指令助记符*/ if(n1) /*空行和注释行的处理*/ continue; if(pcpos = strchr(a_line, :) != NULL) sscanf(a_line,%:,biaohao_temp); save_biaohao(biaohao_temp, line_num, hd_biaohao); else if(strstr(a_line,WORD) != NULL) sscanf(a_line,%*s %s,string); zijie = 2;
30、if(pcpos = strchr(string, ) != NULL) /*若变量为数组格式*/ sscanf(string, %*%, var_size_str);/*存变量大小*/ var_size = atoi(var_size_str); else var_size = 1; save_var(a_line, pianyi_num, var_size, hd_var, zijie); pianyi_num += 2*var_size; /*一个word型值占俩字节*/ line_num-; else if(strstr(a_line,BYTE) != NULL) sscanf(a_l
31、ine,%*s %s,string); zijie = 1; if(pcpos = strchr(string, ) != NULL) /*若变量为数组格式*/ sscanf(string, %*%, var_size_str);/*存变量大小*/ var_size = atoi(var_size_str); else var_size = 1; save_var(a_line, pianyi_num, var_size, hd_var, zijie); pianyi_num += var_size; /*一个byte型值占一个字节*/ line_num-; line_num+; fclose
32、(pfin); pfin=fopen(argv1,r); while(!feof(pfin) fgets(a_line, 200, pfin); if(pcpos = strchr(a_line,#)!=NULL) *pcpos=0; if(pcpos = strstr(a_line,WORD)!=NULL) continue; else if(pcpos = strstr(a_line,BYTE)!=NULL) continue; else if(pcpos = strchr(a_line, :) != NULL) /*去掉标号*/ for(i = 0; a_linei != :; i+)
33、a_linei= ; a_linei= ; n=sscanf(a_line,%s,op_sym); if(n 31) printf(ERROR: %s is a invalid instruction! n, a_line); exit(-1); fprintf(pfout, 0x%08lxn, transtocode(a_line, op_num, hd_biaohao, hd_var); write_var(pfout, hd_var); /*将变量的值以十六进制输出进pfout文件中*/ fclose(pfin); fclose(pfout); printf(success!n); re
34、turn 1;/*由指令助记符得到指令操作码*/int getinscode(const char *op_sym) int i; for(i = 0; i 32; i+) if(strcmp(g_ins_namei, op_sym) = 0) break; return i;/*将指令翻译成目标代码, ins_num为指令操作码*/unsigned long transtocode(char *ins_line, int ins_num, biaohao *hd1, var *hd2) unsigned long op_code; /*op_code为指令操作码*/ unsigned lon
35、g arg1, arg2, arg3; /*arg为寄存器对应的编号*/ unsigned long ins_code = 0ul; char op_sym8, reg08, reg18, reg28;/*op_sym为指令助记符reg为寄存器名*/ unsigned long addr; int immed, port, n; char string20; biaohao *p1; var *p2; switch(ins_formatins_num) case 1: /*类型 op(5b) + padding(27)*/ op_code = ins_num; ins_code = op_co
36、de 27; break; case 2: /*类型op(5b) + padding(3b) + address(24b)*/ n = sscanf(ins_line, %s %s, op_sym, string); if(nnext; p1 != NULL; p1 = p1-next) if(strcmp(p1-name,string)=0) break; if(p1 = NULL) printf(ERROR:%s,ins_line); exit(-1); addr = (unsigned long)(p1-line_num)*4); op_code = getinscode(op_sym);/*由助记符得到操作码*/ ins_code = (op_code 27) | (addr & 0x00ffffff); break; case 3: /*类型op(5b) + reg0(3b) + padding(24b)*/