ARM 流水线技术:
常常在讲ARM流水线的时候,很多同学就很晕,后来调查了一下,大多数同学都是不同熟悉指令导致的。为了让大家更好的流水线,在这里先简单的介绍ARM的一部分指令集。
一、部分ARM指令
在学ARM指令之前,要知道一个常识,就是我们的指令可是直接给ARM核执行的,每条指令都应该包含两部分内容:(1)执行的指令 (2)操作的数据。
思考,此时我们把数据都存储在哪里呢?
要想回答这个问题,必须知道ARM核能直接操作的存储单元是谁呀,呵呵,是寄存器。对,我们就把我们的数据存放在寄存器中,然后ARM直接就可以从寄存器中读取数据,根据相应的指令,做出相应的处理。当然ARM也可以读取内存中的数据的,此时需要用到ldr指令。
1.加载数据到寄存器
(1)加载一个立即数到寄存器
mov 寄存器,#立即数
什么是立即数呢?先不要研究这个问题,研究这个问题没有意义,如果你写的不是的数据不是一个合法的立即数,编译器会报错的。不过,可以告诉大家的是,能用一个字节标识的数据,都可以认为是合法立即数。
还有一个地方要注意,在立即数的前面要加上"#",表示后面的数据是一个立即数。
例如:给r0,寄存器赋值为10
movr0,#10
(2)用ldr加载任意数据到寄存器
ldr 寄存器,=数据
记住这么写就可以了,后面我们会解释为什么的。
例如:将0x12345678加载到r1寄存器
ldr r1,=0x12345678
(3)将寄存器的值写到内存
str 寄存器,[地址]
注意,这里的"[]"一定不能丢了
例如,将寄存器r1的值,写到0x30008000这个地址
movr1,#100
ldr r2,=0x30008000
str r,[r2]
(4)从内存中加载数据到寄存器
ldr 寄存器,[地址]
注意,这里的"[]"一定不能丢了
例如:从0x30008000这个地址读取数据到r1寄存器
ldrr0,=0x30008000
ldrr1,[r0]
2.加、减运算指令
(1)加法指令
add 寄存器,操作数1,操作数2
这里的寄存器是用来存放,操作数1,操作数2相加的结果的,其中操作数1必须是寄存器,操作2可以是寄存器也可以是立即数
例如:
addr2,r0,r1
等价于r2 = r0 + r1
addr2,r0,#3
等价于r2 = r0 + 3
(2)减法指令
sub 寄存器,操作数1,操作数2
这里的寄存器是用来存放,操作数1 减去 操作数2的结果的,其中操作数1必须是寄存器,操作2可以是寄存器也可以是立即数
例如:
subr2,r0,r1
等价于r2 = r0 - r1
subr2,r0,#3
等价于r2 = r0 - 3
3.与,或运算符
(1)与运算
and 寄存器,操作1,操作数2
这里的寄存器是用来存放,操作数1 与 上 操作数2的结果的,其中操作数1必须是寄存器,操作2可以是寄存器也可以是立即数
例如:将r0寄存器的值与上0x23的结果写入r0
andr0,r0,#0x23
(2)或运算
orr 寄存器,操作1,操作数2
这里的寄存器是用来存放,操作数1 或 上 操作数2的结果的,其中操作数1必须是寄存器,操作2可以是寄存器也可以是立即数
例如:将r0寄存器的值或上0x23的结果写入r0
orrr0,r0,#0x23
4.跳转指令
(1)b 指令
b指令跳转到指定的地址执行程序。
例如: 跳转到地址0x30008000地址去执行
b 0x30008000
有些时候,在汇编程序里面,我们可以设置一个标号,可以直接通过b指令跳到对应的标号
b next
....
....
next:
....
stop:
b stop
(2)bl 指令
bl指令在跳转到指定地址执行程序前,将下一条指令的地址拷贝到R14(lr)链接寄存器中。
在这里大家可以思考一下,bl指令比b指令有哪些优势?
5.条件执行
所有的ARM指令都可以条件执行,而Thumb指令只有B(跳转)指令具有条件执行 功能。如果指令不标明条件代码,将默认为无条件(AL)执行。
常用的条件:
EQ 相等
NE 不相等
....
还有很多哦,先只说两个
例如:
start:
subr0,r0,#1
cmp r0,#3
beq next
bne start
6.多寄存器加载指令LDM
多寄存器加载可以实现在一组寄存器和一块连续的内存单元之间传输数据。LDM为加载多个寄存器;
LDM{cond}<模式> Rn{!},reglist{^}
cond:指令执行的条件;
模式:控制地址的增长方式,一共有8种模式;
!:表示在操作结束后,将最后的地址写回Rn中;
reglist:表示寄存器列表,可以包含多个寄存器,它们使用“,”隔开,如{R1,R2,R6-R9},寄存器由小到大排列;
^:加入该后缀后,进行数据传送且寄存器列表不包含PC时,加载/存储的寄存器是用户模式下的,而不是当前模式的寄存器。若在LDM指令且寄存器列表中包含有PC时使用,那么除了正常的多寄存器传送外,还将SPSR也拷贝到CPSR中,这可用于异常处理返回。注意:该后缀不允许在用户模式或系统模式下使用。
例如:
LDMIA R13!, {R0-R3}
LDM指令的功能是将对应地址空间的内容加载到寄存器,IA(increase after)指的是加载完后,地址自动加4
! 指的是地址增加后,要写入R13寄存器
例如:R13的值是0x30000,
指令执行完后,R0的值为12,R1的值为13,R2的值为14,R3的值为15,R13的值为0x300f
好了就这么多吧,学这些指令的目的是让大家能看懂流水线做的铺垫,后面我们会单独花时间来讲解指令集的。
二、ARM的流水线
一条指令执行的过程
简单来说,执行某条指令至少要通过取指、译码、执行三个步骤。这就好像盲人在吃饭,第一步是用筷子夹出要吃的东西(从内存中取出指令),第二步是把吃的东西举到鼻子底下闻一下看看是否能吃(分析该指令),第三步是放到嘴里吃(执行指令)。
我们假设盲人又只有一只手,而每一个步骤都要一秒钟的时间,那么这位盲人至少需要三秒钟才能吃到一样东西,很显然这种吃饭的方法效率低。所以,如果CPU也采取同样的方法,像上图那样去执行一条指令,那就意味着 CPU需消耗3个指令周期才能完成一个动作,可见其运行效率的低下。
为了弥补这个问题,ARM采用了一种多级流水线的指令执行方式。例如,在ARM7就采用了三级流水线的处理方法,如下图所示:
如上图,CPU采用流水线作业的方式,在大多数情况下,是利用三个时钟周期的时间去执行三条指令,从而大大提高了代码运行效率。
这就好像一位乐于助人的科学家,知道了盲人吃饭的故事之后,给这位盲人制作了两只机械手,现在盲人已经有三只手了,那么他会怎样吃饭呢?当他的第一只手把吃的送到嘴里吃的时候(执行指令),第二只手已经将另外的食物凑到鼻子底下闻了(分析指令),而第三只手此时正在从盘子里夹第三样东西呢。从此,盲人吃饭的效率就提高了三倍。
理解了吗,这就是流水线的好处。下面,来看一些典型的流水线。
1.ARM7的三极流水线
为增加处理器指令流的速度,ARM7 系列使用3级流水线.
允许多个操作同时处理,而非顺序执行。
PC指向正被取指的指令,而非正在执行的指令
(1)最佳流水线实例:
该例中用6个时钟周期执行了6条指令
所有的操作都在寄存器中(单周期执行)
指令周期数 (CPI) = 1
呵呵,CPI=1是最理想的流水线了,但在很多时候,很难达到这样的效果。例如,出现多周期指令、跳转分支指令和中断发生的时候,流水线都会阻塞,而且相邻指令之间可能因为寄存器冲突导致流水线阻塞,降低流水线的效率。
(2)带有存储器访问指令的流水线
该例中,用6周期执行了4条指令
指令周期数 (CPI) = 1.5
通过上图可以知道,由于LDR指令要从内存中加载数据到寄存器。所以整个过程就可以分为访问内存,然后写寄存器。注意此时的访问内存和写寄存器都是在执行单元进行的,此时仍然占用执行单元。这样后面一条指令就不能使用执行单元,此时流水线被阻隔。
(3)含有分支指令的流水线
流水线被阻断
注意:内核运行在ARM状态
分支指令在其第一周期计算分支的目的地,同时在现行PC处完成一次指令预取。这种预取在任何情况下都要做的,因为当判决地址产生时已来不及停止预取。
第二个周期在分支的目标地址完成取指,而返回地址则存于R14如果link位已设置。
第三周期完成目标地址+4的取指,重新填满流水线,并且如果跳转是带链接的还要修改R14(减去4)以便简单地返回,需要三个时钟周期来执行,它所干的事情就是修改lr的值即将lr的值减去4。
(4)中断流水线
周期1: 内核被告知有中断
IRQ在现行指令执行完之前不会被响应( MUL and LDM/STM 指令会有长的延迟)
解码阶段:中断被解码(中断已使能,设置了相应标志位… )。如果中断被使能和服务,正常的指令将不会被解码。
周期 2: 此时总是进入ARM状态.
执行中断 ( 获取IR向量的地址), 保存 CPSR 于 SPSR, 改变CPSR模式为 IRQ 模式并禁止进一步的 IRQ 中断输入。
周期 3: 保存 PC (0x800C) 于 r14_irq, 从IRQ异常处理向量处取指
周期 4: 解码向量表中的指令; 调整r14irq 为0x8008,即将r14irq的值减去4
周期 4和 5: 无有用的指令取指,由于周期 6的跳转
周期 6: 取异常处理子程序的第一条指令;从子程序返回: SUBSpc,lr,#4
这将恢复工作模式并从响应中断前的下一条指令处取指
如果有多个中断,需堆栈保存返回地址
2.ARM9的五级流水线
从上图可以看到,ARM9设计的5级流水线,是将三极流水线中执行单元做的访问内存 和寄存器回写拆分成两个单元进行执行。
(1)ldr互锁
五级流水线只存在一种互锁,即寄存器冲突。读寄存器是在译码阶段,写寄存器是在回写阶段。如果当前指令(A)的目的操作数寄存器和下一条指令(B)的源操 作数寄存器一致,B指令就需要等A回写之后才能译码。这就是五级流水线中的寄存器冲突。
本例中,用了7个时钟周期执行6条指令, CPI =1.2 。
LDR指令之后立即跟一条数据操作指令,由于使用了相同的寄存器,将会导致互锁 。
(2)最佳流水线
本例中,用了6个时钟周期执行6条指令, CPI =1。
LDR指令没有引起流水线互锁
(3)LDM互锁 1
由于LDMIA在执行的时候每次将内存的数据加载到寄存器后,会自动加地址值+4,注意这是一个加法操作,必须占用E单元。从上图可以看到在2,3,4之后。