MIPS

from :http://hi.baidu.com/fei4630365/blog/item/7d082ee83990de2827979168.html

1.  MIPS 寄存器介绍

32个通用寄存器:

寄存器编号  助记符     用法
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           s8/fp      第9个寄存器变量。子函数可以用来做桢指针
31           ra         子函数的返回地址

寄存器名约定与使用:

*at: 这个寄存器被汇编的一些合成指令使用。如果你要显式地使用这个寄存器(比如在异常处理程序中保存和恢复寄存器),有一个汇编directive 可被用来禁止汇编器在directive 之后再使用at 寄存器(但是汇编的一些宏指令将因此不能再可用)。
*v0, v1: 用来存放一个子程序(函数)的非浮点运算的结果或返回值。如果这两个寄存器不够存放需要返回的值,编译器将会通过内存来完成。
*a0-a3: 用来传递子函数调用时前4 个非浮点参数。在有些情况下,这是不对的。
* t0-t9: 依照约定,一个子函数可以不用保存并随便的使用这些寄存器。在作表达式计算时,这些寄存器是非常好的暂时变量。编译器/程序员必须注意的是,当调用一个子函数时,这些寄存器中的值有可能被子函数破坏掉。
*s0-s8: 依照约定,子函数必须保证当函数返回时这些寄存器的内容必须恢复到函数调用以前的值,或者在子函数里不用这些寄存器或把它们保存在堆栈上并在函数退出时恢复。这种约定使得这些寄存器非常适合作为寄存器变量或存放一些在函数调用期间必须保存原来值。
* k0, k1: 被OS 的异常或中断处理程序使用。被使用后将不会恢复原来的值。因此它们很少在别的地方被使用。
* gp: 如果存在一个全局指针,它将指向运行时决定的,你的静态数据(static data)区域的一个位置。这意味着,利用gp 作基指针,在gp 指针32K 左右的数据存取,系统只需要一条指令就可完成。如果没有全局指针,存取一个静态数据区域的值需要两条指令:一条是获取有编译器和loader 决定好的32 位的地址常量。另外一条是对数据
的真正存取。为了使用gp, 编译器在编译时刻必须知道一个数据是否在gp 的64K 范围之内。通常这是不可能的,只能靠猜测。一般的做法是把small global data (小的全局数据)放在gp 覆盖的范围内(比如一个变量是8 字节或更小),并且让linker 报警如果小的全局数据仍然太大从而超过gp 作为一个基指针所能存取的范围。并不是所有的编译和运行系统支持gp 的使用。
*sp: 堆栈指针的上下需要显示的通过指令来实现。因此MIPS 通常只在子函数进入和退出的时刻才调整堆栈的指针。这通过被调用的子函数来实现。sp 通常被调整到这个被调用的子函数需要的堆栈的最低的地方,从而编译器可以通过相对於sp 的偏移量来存取堆栈上的堆栈变量。详细可参阅10.1 节堆栈使用。
* fp: fp 的另外的约定名是s8。如果子函数想要在运行时动态扩展堆栈大小,fp 作为桢指针可以被子函数用来记录堆栈的情况。一些编程语言显示的支持这一点。汇编编程员经常会利用fp 的这个用法。C 语言的库函数alloca()就是利用了fp 来动态调整堆栈的。如果堆栈的底部在编译时刻不能被决定,你就不能通过sp 来存取堆栈变量,因此fp 被初始化为一个相对与该函数堆栈的一个常量的位置。这种用法对其他函数是不可见的。
* ra: 当调用任何一个子函数时,返回地址存放在ra 寄存器中,因此通常一个子程序的最后一个指令是jr ra.
子函数如果还要调用其他的子函数,必须保存ra 的值,通常通过堆栈。
2. 寄存器 寻址
加载和存储:寻址方式

MIPS 只有一种寻址方式。任何加载或存储机器指令可以写成
lw $1, offset($2)
你可以使用任何寄存器来作为目标和源寄存器。offset 偏移量是一个有符号的16 位的数字(因此可以是在-32768 与32767 之间的任何一值)。用来加载的程序地址是源寄存器与偏移量的和所构成的地址。这种寻址方式一般已足够存取一个C 语言的结构(偏移量是这个结构的起始地址到所要存取的结构成员之间的距离)。这种寻址方式实现了一个通过一个常量来索引的数组;并足够使得可以存取堆栈上的函数变量或桢指针;可以提供一个比较合适大小的以gp 为基址的全局空间以存取静态和外部数据。
汇编器提供一个简单直接存取方式的汇编格式从而可以加载一个在连接时刻才能决定地址的变量的值。
相对GP的寄存器的寻址

基本地址空间:

32 位下,程序地址空间划分为4 个大区域。每个区域有一个传统的名字。对于在这些区域的地址,各自有不同的属性:

       kuseg: 0x000 0000 – 0x7FFF FFFF (低端2G):这些地址是用户态可用的地址。在有MMU 的机器里,这些地址将一概被MMU 作转换。除非MMU 的设置被建立好,这2G 地址是不可用的。对于没有MMU 的机器,存取这2G 地址的操作与具体机器相关。你的CPU 具体厂商提供的手册将会告诉你关于这方面的信息。如果想要你的代码在有或没有MMU 的MIPS 处理器之间有兼容性,尽量避免这块区域的存取。
      kseg0: 0x8000 0000 – 0x9FFF FFFF(512M): 这些地址映射到物理地址简单的通过把最高位清零,然后把它们映射到物理地址低段512M(0x0000 0000 – 0x1FFF FFFF)。因为这种映射是很简单的,通常称之为“非转换的“地址区域。几乎全部的对这段地址的存取都会通过快速缓存(cache)。因此在cache 设置好之前,不能随便使用这段地址。通常一个没有MMU 的系统会使用这段地址作为其绝大多数程序和数据的存放位置。对于有MMU 的系统,操作系统核心会存放在这个区域。
       kseg1: 0xA000 0000 – 0xBFFF FFFF(512M): 这些地址通过把最高3 位清零的方法来映射到相应的物理地址上,与kseg0 映射的物理地址一样。但kseg1 是非cache 存取的。kseg1 是唯一的在系统重启时能正常工作的地址空间。这也是为什么重新启动时的入口向量是0xBFC0 0000。这个向量相应的物理地址是0x1FC0 0000。你将使用这段地址空间去存取你的初始化ROM。大多数人在这段空间使用I/O 寄存器。如果你的硬件工程师要把这段地址空间映射到非低段512M 空间,你得劝说他。
       kseg2: 0xC000 0000 – 0xFFFF FFFF (1G): 这段地址空间只能在核心态下使用并且要经过MMU 的转换。在MMU 设置好之前,不能存取这段区域。除非你在写一个真正的操作系统,一般来说你不需要使用这段地址空间。
要注意的是64 位内存映象是包含在32 位内存映象里面的。这是个有点奇怪的方法。当模拟一个32 位指令集时,寄存器存放的是其32 位的带符合位扩展的64 位值。因此,一个32 位程序存取的是64 位程序空间的最低和最高的2G。换句话说,64 位CPU 的地址空间的最低和最高区域是和32 位情况下一样的,64 位扩展的地址部分在这两者之间。

 3.  MISP 指令集

MIPS CPU的一次操作可加载或存储1到8个字节的数据。由于乘法的结果返回的速度不足以使下一条指令能够自动得到这个结果,乘法结果寄存器是互锁的(interlocked)。在乘法操作完成之前试图读取结果寄存器就是导致CPU停止运行,直到完成。
和其他一些更简单的RISC体系结构相比,MIPS体系结构的目标之一是:体系结构朝着64位发展,从而使得地址的段式结构变得没有任何必要。(在64位版本的X86核PowerPC中还有这个负担)
功能分组:
      空操作:nop、ssnop(不能和其他指令同时发射,至少需要一个时钟周期)
      寄存器间的数据传送指令:move、movf、movt、movn、movz(后四个为条件传递指令)
     常数加载指令:dla、la(获取某些标号地址或程序中变量地址的宏指令);dli、li(加载常数立即数指令);lui(加载高位立即数指令)
       算术/逻辑操作指令:addu、addiu、daddu,daddiu(加法指令);dsub、sub(会触发溢出陷入的减法操作);dsubu、subu(普通减法指令);abs、dabs(求绝对值操作);dneg、neg、dnegu、negu(一元非操作);and、andi、or、ori、xor、xori、nor、not(按位逻辑指令);drol、dror、rol、ror(循环左移和右移);dsll、dsll32、dsllv(64位左移,低位补零);dsra、dsra32、dsrav(64位算术右移指令);dsrl、dsrl32、dsrlv(64位逻辑右移指令);sll、sllv(32位左移指令);sra、srav(32位算术右移指令);srl、srlv(32位逻辑右移指令);slt、slti、sltiu、sltu(硬件指令,条件满足就写入1,否则写0);seq、sge、sgeu、sgt、sgtu、sle、slue、sne(根据更复杂的条件设置目的寄存器的宏指令)
      整数乘法、除法以及求余指令:ddiv、ddivu、div、divu(整数除法的3操作数宏指令分别处理64位或32位有符号或无符号数);divo、divou(明确该指令是带有溢出检查的除法指令);dmul、mul(3操作数64位或32位乘法指令,没有溢出检查);mulo、mulou、dmulo、dumlou(乘法宏指令,如果结果不能存入一个通用寄存器,发生溢出,触发异常);dmult、dmultu、mult、multu(执行有符号/无符号32/64位乘法的机器指令);drem、dremu、rem、remu(求余操作);mfhi、mflo、mthi、mtlo(用于访问整数乘除单元的结果寄存器hi和lo)
       存取指令(内存访问指令):lb、lbu(加载一个字节,高位可以补零,或进行符号扩展,以补充整个寄存器的长度);ld(加载一个双字);ldl、ldr、lwl、lwr、sdl、sdr、swl、swr(向左、向右加载、存储一个字、双字);lh、lhu(加载一个半字,高位可以补零,或进行符号扩展,以补充整个寄存器的长度);lw、lwu(加载一个字);pref、prefx(把数据预取到缓冲);sb、sd、sh、sw(存储字节、双字、半字、字);uld、ulh、ulhu、ulw、usd、usw、ush(地址非对齐的数据存取宏指令);l.d、l.s、s.d、s.s(存取双精度和单精度浮点数的指令,地址必须对齐);ldxcl、lwxcl、sdxcl、swxcl(采用基址寄存器+偏移寄存器的寻址方式存取指令);
       跳转、分支和子程序调用指令:j(无条件跳转到一个绝对地址,访问256M的代码空间);jal、jalr(直接或间接子程序调用,这种跳转不仅能跳转到指定地址,而且可以顺便把返回地址(当前指令地址+8)放到ra寄存器中);b(基于当前指令地址的无条件相对跳转);bal(基于当前地址的函数调用指令);bc0f、bc0f1、bc0t、bc0t1、bc2f、bc2f1、bc2t、bc2t1(根据协处理器0和2的条件标志进行跳转);bc1f、bc1f1、bc1t、bc1t1(根据浮点条件标志位进行跳转);beq、beq1、beqz、beqz1、bge、bge1、bgeu、bgeu1、bgez、bgez1、bgt、bgt1、bgtu、bgtu1、bgtz、bgtz1、ble、ble1、bleu、bleu1、blez、blez1、blt、blt1、bltu、bltu1、bltz、bltz1、bne、bnel、bnez、bnezl(双操作数和单操作数的比较跳转指令);bgeza1、bgeza11、bltza1、bltza11(如果需要,这些指令是用于有条件函数调用的原始机器指令);
       断点及陷阱指令:break(产生一个“断点”类型的异常);sdbbp(产生EJTAG异常的断点指令);syscall(产生一个约定用于系统调用的异常类型);teq、teqi、tge、tgei、tgeiu、tgeu、tlt、tlti、tltiu、tltu、tne、tnei(条件异常指令,对一个或两个操作数进行条件测试);
       协处理器0的功能:cfc0、ctc0(把数据拷进和拷出协处理器0的控制寄存器);mfc0、mtc0、dmfc0、dmtc0(在通用寄存器和协处理器0寄存器之间交换数据);cfc2、ctc2、dmfc2、dmtc2、mfc2、mtc2(协处理器2的指令);
4。 MIPS编程:

汇编语言
#include:利用C预编译器cpp提供的功能,为常数定义一些简单的文本替代宏操作。
.set:指导汇编器如何工作(排列指令顺序)。.set noreorder将底层的分支延迟槽暴露出来,那么我们就必须确保加载的数据不被紧接着的指令使用。
LEAF是在mips/asm.h中定义的宏,用于定义简单的例程,非LEAF例程需要大量的工作来存储变量,返回地址等信息。
句法:标号和变量的标识符可以是C语言中的合法标识符,同时可以包含“$”和“.”。MIPS汇编语言中并没有对应于C语言“指针”的操作符,但当汇编器需要一个指针大小的数据时,会用一个标号来代替。“.”表示当前指令或数据声明的地址,甚至对它们进行一些简单数学运算。在带常量(立即数)的计算指令中,一般的算术指令立即数都采用符号扩展,逻辑操作都采用补0扩展。
寻址模式:硬件只支持一种寻址方式,基址寄存器+偏移,然而,汇编器会调整代码,可以以多种方式寻址:
      直接寻址:数据标号或者外部变量名
      直接+索引:以标号位置为基准,由寄存器指定偏移量
      常量:大数,绝对32位长地址
      寄存器间接寻址:基址寄存器+偏移
目标文件和内存布局:
    ROM程序的目标代码段和典型内存布局入下图所示。_ftext为代码段起始点;etext为代码段结束点;_fdata为初始化数据起始点;edata为初始化数据结束点;_fbss未初始化数据段起始点;end为未初始化数据结束点。
   C和其他程序语言的编译器都是将整个程序划分为许多模块,分别编译。为了使彼此能配合操作系统工作,编译的代码模块之间遵循一些调用接口约定(由编译器强制,因此汇编程序员也强制),包括寄存器的使用,堆栈建立和参数传递等。
MIPS没有专门的I/O指令,这说明I/O操作和所选地址的普通的加载存储操作一样。关键字volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。用C写设备驱动比汇编容易,但使用中要注意合理选择。特别要注意了解工具链的工作方式,如何在你的高级语言代码中嵌入低级汇编指令,并确保系统按照你期望的方式运行。
Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s