关于MIPS

MIPS有32个通用寄存器($0-$31),各寄存器的功能及汇编程序中使用约定如下:

下表描述32个通用寄存器的别名和用途:

REGISTER

NAME

USAGE

$0

$zero

常量0(constant value 0)

$1

$at

保留给汇编器(Reserved for assembler)

$2-$3

$v0-$v1

函数调用返回值(values for results and expression evaluation)

$4-$7

$a0-$a3

函数调用参数(arguments)

$8-$15

$t0-$t7

暂时的(或随便用的)

$16-$23

$s0-$s7

保存的(或如果用,需要SAVE/RESTORE的)(saved)

$24-$25

$t8-$t9

暂时的(或随便用的)

$28

$gp

全局指针(Global Pointer)

$29

$sp

堆栈指针(Stack Pointer)

$30

$fp

帧指针(Frame Pointer)

$31

$ra

返回地址(return address

$0:

即$zero ,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式。
           move $t0,$t1
       实际为
           add $t0,$0,$t1
       使用伪指令可以简化任务,汇编程序提供了比硬件更丰富的指令集。
$1:

即$at,该寄存器为汇编保留 ,由于I型指令的立即数字段只有16位,在加载大常数时,编译器或汇编程序需要
       把大常数拆开,然后重新组合到寄存器里。比如加载一个32位立即数需要 lui(装入高位立即数)和addi两条
       指令。像MIPS程序拆散和重装大常数由汇编程序来完成,汇编程序必需一个临时寄存器来重组大常数,这
       也是为汇编 保留$at的原因之一。
$2..$3:

($v0-$v1)用于子程序的非浮点结果或返回值, 对于子程序如何传递参数及如何返回,MIPS范围有一套约
       定,堆栈中少数几个位置处的内容装入CPU寄存器,其相应内存位置保留未做定义,当这两个寄存器不够存
       放返回值时,编译器通过内存来完成。
$4..$7:

($a0-$a3)用来传递前四个参数给子程序,不够的用堆栈 。 a0-a3和v0-v1以及ra一起 来支持子程序/过程
       调用,分别用以传递参数,返回结果和存放返回地址。当需要使用更多的寄存器时,就需要堆栈(stack)
       了,MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。
$8..$15:

($t0-$t7)临时寄存器, 子程序可以使用它们而不用保留。
$16..$23:

($s0-$s7)保存寄存器,在过程调用过程中需要保 留(被调用者保存和恢复,还包括 $fp和$ra ),MIPS
       提供了临时寄存器和保存寄存器,这样就减少了寄存器溢出(spilling,即将不常用的变量放到存储器的过程),
       编译器在编译一个叶(leaf)过程(不调用其它过程的过程)的时候,总是在临时寄存器分配完了才使用需要
       保存的寄存器。
$24..$25:($t8-$t9)同($t0-$t7)
$26..$27:($k0,$k1)为操作系统/异常处理保留,至少要预留一个 。 异常(或中断)是一种不需要在程序中显示
       调用的过程。MIPS有个叫异常程序计数器(exception program counter,EPC)的寄存器,属于CP0寄存器,

       用于保存造成异常的那条指令的地址。查看控制寄存器的唯一方法是把它复制到通用寄存器里,指令mfc0
       (move from system control)可以将EPC中的地址复制到某个通用寄存器中,通过跳转语句(jr),程序可以返
       回到造成异常的那条指令处继续执行。MIPS程序员都必须保留两个寄存器$k0和$k1,供操作系统使用。

       发生异常时,这两个寄存器的值不会被恢复,编译器也不使用k0和k1, 异常处理函数可以将返回地址放到这
       两个中的任何一个,然后使用jr跳转到造成异常的指令处继续执行 。
$28:($gp) 为了简化静态数据的访问,MIPS软件保留了一个寄存器:全局指针gp(global pointer,$gp),全局指针
       只想静态数据区中的运行时决定的地址,在存取位于gp值上下32KB范围内的数据时,只需要一条以gp为基
       指针的指令即可。在编译时,数据须在以gp为基指针的64KB范围内。
$29:($sp)MIPS硬件并不直接支持堆栈 ,你可以把它用于别的目的,但为了使用别人的程序或让别人使用你的程
       序, 还是要遵守这个约定的,但这和硬件没有关系。
$ 30:($fp)GNU MIPS C编译器使用了帧指针(frame pointer), 而SGI的C编译器没有使用, 而把这个寄存器当作保
       存寄存器使用($s8), 这节省了调用和返回开销,但增加了代码生成的复杂性。
$31:($ra)存放返回地址, MIPS有个jal(jump-and-link,跳转并 链接)指令,在跳转到某个地址时,把下一条指令的
       地址放到$ra中。用于支持子程序,例如调用程序把参数放到$a0~$a3,然后jal X跳到X过程,被调过程完成后

       把结果放到$v0,$v1,然后使用jr $ra返回。

***

MIPS指令特点:


1、所有指令都是32位编码;
2、有些指令有26位供目标地址编码;有些则只有16位。因此要想加载任何一个32位值,就得用两个加载指令。16位的目标地址意味着,指令的跳转或子函数的位置必须在64K以内(上下32K);
3、所有的动作原理上要求必须在1个时钟周期内完成,一个动作一个阶段;
4、有32个通用寄存器,每个寄存器32位(对32位机)或64位(对64位机);
5、本身没有任何帮助运算判断的标志寄存器,要实现相应的功能时,是通过测试两个寄存器是否相等来完成的;
6、所有的运算都是基于32位的,没有对字节和对半字的运算(MIPS里,字定义为32位,半字定义为16位);
7、没有单独的栈指令,所有对栈的操作都是统一的内存访问方式。因为push和pop指令实际上是一个复合操作,包含对内存的写入和对栈指针的移动;

8、由于MIPS固定指令长度,所以造成其编译后的二进制文件和内存占用空间比x86的要大,(x86平均指令长度只有3个字节多一点,而MIPS是4个字节);

9、寻址方式:只有一种内存寻址方式。就是基地址加一个16位的地址偏移;

10、内存中的数据访问必须严格对齐(至少4字节对齐)

11、跳转指令只有26位目标地址,再加上2位的对齐位,可寻址28位的空间,即256M。意思即是说,在一个C程序内,goto语句只能跳转到它之前的128M和之后的128M这个地址空间之内

12、条件分支指令只有16位跳转地址,加上2位的对齐位,共18位寻址空间,即256K。意思即是说,在一个C程序内,if语句只能跳转到它之前的128K和之后的128K这个地址空间之内;

13、MIPS默认不把子函数的返回地址(就是调用函数的受害指令地址)存放到栈中,而是存放到$31寄存器中;这对那些叶子函数有利。如果遇到嵌套的函数的话,有另外的机制处理;

14、流水线效应。由于采用了高度的流水线,结果产生了一些对程序员来说可见的效应,需要注意。最重要的两个效应就是分支延迟效应和载入延迟效应。
    a 任何一个分支跳转语句后面的那条语句叫做分支延迟槽。实际上在程序执行到分支语句时,当他刚把要跳转到的地址填充好(到代码计数器里),还没完成本条 指令,分支语句后面的那个指令就执行了。这是因为流水线效应,几条指令同时在执行,只是处于不同的阶段。具体看书上说提前半条指令执行,没看懂。分支延迟 槽常用被利用起来完成一些参数初始化等相关工作,而不是被浪费了。
    b 载入延迟是这样的。当执行一条从内存中载入数据的命令时,是先载入到高速缓冲中,然后再取到寄存器中,这个过程相对来说是比较慢的。在这个过程完成之 前,可能已经有几条在流水线上的指令被执行了。这几条在载入指令后被执行的指令就被称作载入延迟槽。现在就有一个问题,如果后面这几条指令要用到载入指令 所载入的那个数据怎么办?一个通用的办法是,把内部锁加在数据载入过程上,这样,当后面的指令要用这条指令时,就只有先停止运行(在ALU阶段),等这条 数据载入指令完成了后再开始运行。

*MIPS指令的五级流水线:每条指令都包含五个执行阶段。
第一阶段:从指令缓冲区中取指令。占一个时钟周期;
第二阶段:从指令中的源寄存器域(可能有两个)的值(为一个数字,指定$0~$31中的某一个)所代表的寄存器中读出数据。占半个时钟周期;
第三阶段:在一个时钟周期内做一次算术或逻辑运算。占一个时钟周期;
第四阶段:指令从数据缓冲中读取内存变量的阶段。从平均来讲,大约有3/4的指令在这个阶段没做什么事情,但它是指令有序性的保证(为什么是保证,我还没看清楚?)。占一个时钟周期;
第五阶段:存储计算结果到缓冲或内存的阶段。占半个时钟周期;
=> 所以一条指令要占用四个时钟周期;

15、MIPS的虚拟地址内存映射空间:
a  0x0000 0000 ~ 0x7fff ffff
用户级空间,2GB,要经MMU(TLB)地址翻译。kuseg。可以控制要不要经过缓冲。

b 0x8000 0000 ~ 0x9fff ffff
kseg0. 这块区域为操作系统内核所占的区域,共512M。使用时,不经过地址翻译,将最高位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。但要经过缓冲区过渡。

c 0xa000 0000 ~ 0xbfff ffff
kseg1. 这块区域为系统初始化所占区域,共512M。使用时,不经过地址翻译,也不经过缓冲区。将最高3位去掉就线性映射到内存的低512M(不足的就裁剪掉顶部)。

d 0xc000 0000 ~ 0xffff ffff
kseg2. 这块区域也为内核级区域。要经过地址翻译。可以控制要不要经过缓冲。

16、MIPS的协处理器
CP0:这是MIPS芯片的配置单元。必不可少,虽然叫做协处理器,但是通常都是做在一块芯片上。绝大部分MIPS功能的配置,缓冲的控制,异常/中断的控制,内存管理的控制都在这里面。所以是一个完整的系统所必不可少的;

17、 MIPS的高速缓冲
MIPS一般有两到三级缓冲,其中第一级缓冲数据和指令分开存储。这样的好处是指令和数据可以同时存取,提高效率。但缺点是提高了复杂度。第二级缓冲和第三级缓冲(如果有的话)就不再分开存放啦。

缓冲的单元叫做缓冲行(cache line)。每一行中,有一个tag,然后后面接的是一些标志位和一些数据。缓冲行按顺序线性排列起来,就组成了整个缓冲。

cache line的索引和存取有一套完整的机制。
18、MIPS的异常机制
精确异常的概念:在运行流程中没有任何多余效应的异常。即当异常发生时,在受害指令之前的指令被完全执行,而受害指令及后面的指令还没开始执行(注:说受 害指令及后面的指令还没做任何事情是不对的,实际上受害指令是处于其指令周期的第三阶段刚完成,即ALU阶段刚完成)。精确异常有有助于保证软件设计上不 受硬件实现的影响。

CP0中的EPC寄存器用于指向异常发生时指令跳转前的执行位置,一般是受害指令地址。当异常时,是返回这个地址继续执行。但如果受害指令在分支延迟槽中,则会硬件自动处理使EPC往回指一条指令,即分支指令。在重新执行分支指令时,分支延迟槽中的指令会被再执行一次。

精确异常的实现对流水线的流畅性是有一定的影响的,如果异常太多,系统执行效率就会受到影响。

*异常又分常规异常和中断两类。常规异常一般为软件的异常,而中断一般为硬件异常,中断可以是芯片内部,也可以是芯片外部触发产生。

异常发生时,跳转前最后被执行的指令是其MEM阶段刚好被执行完的那条指令。受害指令是其ALU阶段刚好执行完的那条指令。

异常发生时,会跳到异常向量入口中去执行。MIPS的异常向量有点特殊,它一般只个2个或几个中断向量入口,一个入口给一般的异常使用,一个入口给 TLB miss异常使用(这样的话,可以省下计算异常类型的时间。在这种机制帮助下,系统只用13个时钟周期就可以把TLB重填好)。

CP0寄存器中有个模式位,SR(BEV),只要设置了,就会把异常入口点转移到非缓冲内存地址空间中(kseg1)。

MIPS系统把重启看作一个不可回归的异常来处理。
冷启动:CPU硬件完全被重新配置,软件重新加载;
热启动:软件完全重新初始化;

MIPS对异常的处理的哲学是给异常分配一些类型,然后由软件给它们定义一些优先级,然后由同一个入口进入异常分配程序,在分配程序中根据类型及优先级确定该执行哪个对应的函数。这种机制对两个或几个异常同时出现的情况也是适合的。

下面是当异常发生时MIPS CPU所做的事情:
a 设置EPC指向回归的位置;
b 设置SR(EXL)强迫CPU进入kernel态,并禁止所有中断响应。
c 设置Cause寄存器,以使软件可以得到异常的类型信息;还有其它一些寄存器在某些异常时会被设置;
d CPU开始从异常入口取指令,然后以后的所有事情都交由软件处理了。

k0和k1寄存器用于保存异常处理函数的地址。
异常处理函数执行完成后,会回到异常分配函数那去,在异常分配函数里,有一个eret指令,用于回归原来被中断的程序继续执行;eret指令会原子性地把中断响应打开(置SR(EXL)),并把状态级由kernel转到user级,并返回原地址继续执行。

19、中断
MIPS CPU有8个独立的中断位(在Cause寄存器中),其中,6个为外部中断,2个为内部中断(可由软件访问)。一般来说,片上的时钟计数/定时器,会连接到一个硬件位上去。

SR(IE)位控制全局中断响应,为0的话,就禁止所有中断;
SR(EXL)和SR(ERL)位(任何一个)如果置1的话,会禁止中断;
SR(IM)有8位,对应8个中断源,要产生中断,还得把这8位中相应的位置1才行;

中断处理程序也是用通用异常入口。但有些新的CPU有变化。

*在软件中实现中断优先级的方案
a 给各种中断定优先级;
b CPU在运行时总是处于某个优先级(即定义一个全局变量);
c 中断发生时,只有等于高于CPU优先级的中断优先级才能执行;(如果CPU优先级处于最低,那么所有的中断都可以执行);
d 同时有多个中断发生时,优先执行优先级最高的那个中断程序;

20、大小端问题
硬件上也有大端小端问题,比如串口通讯,一个字节一个字节的发,首先是低位先发出去。
还有显卡的显示,比如显示黑白图像,在屏幕上一个点对应显存中的一位,这时,这个位对应关系就是屏幕右上角那个点对应显存第一个字节的7号位,即最高位。第一排第8位点对应第一个字节的0号位。

posted @ 2023-10-12 16:47  Yuxi001  阅读(60)  评论(0编辑  收藏  举报