本节主要分两大部分来进行讲解,第一部分是指令,第二部分是寄存器。在讲解完这些知识后,将对本次实验的两个程序进行分析,以便于后续编程。
指令在计算机内部是以一系列或高或低的电信号表示的,形式上和数的表示相同。实际上,指令的各部分都可看成一个独立的数,将这些数拼接在一起就形成了指令。
指令的布局形式叫作指令格式(instruction format)。从二进制位的数目可以看出,LEGv8指令占32位,即一个字或半个双字。遵循简单源于规整的原则,所有LEGv8指令都是32位长。
为了与汇编语言区分开,指令的数字形式称为机器语言(machine language),这样的指令序列称为机器码。
LEGv8中的汇编语言如下图所示,当你遇到了一条指令,不知道它怎么执行的时候,请回来看看这张图,在里面找一下你要的指令,相信你很快可以得到答案。
LEGv8中的指令格式一共可分为6类:R型(算术运算指令格式,如ADD、AND、LSL等)、I型(立即数格式,如ADDI、ORRI等)、D型(数据传输格式,如LDUR、STUR等)、B型(无条件分支格式,B和BL)、CB型(条件分支格式,B.cond、CBZ、CBNZ)和IM型(宽立即数格式,MOVZ、MOVK)。在我们的实验中,只关心前5类。各指令的格式如下:
注:这张表很重要,后面针对不同的指令的编程都是参考这张表来开展的。
为了便于讨论,将 LEGv8 指令中R型指令各字段命名如下:
各字段名称及含义如下:
opcode:操作码,指明指令完成的基本操作。
Rm:第二个源操作数寄存器。
shamt:位移量。只有在LSL、LSR这种移位指令中才会用到,其余时间都为0。
Rn:第一个源操作数寄存器。
Rd:目的操作数寄存器,存放操作结果。
LEGv8中的逻辑操作如下图所示:
第一类逻辑操作称为移位(shift),将一个双字的所有位都向左或向右移动,并在空出的位上填充0。
左移和右移这两条指令在LEGv8中的确切名字是逻辑左移LSL(logical shift left)和逻辑右移LSR(logical shift right)。下面的指令完成左移操作,假设源操作数在X19中,结果存储到X11中:
LSL X11, X19, #4 // reg X11 = reg X19 << 4 bits
前面介绍R型指令格式时没有解释shamt字段
,在移位指令中该字段用于表示移位量(shift amount)。因此,上述指令对应的机器语言是:
LSL对应的opcode字段为1691,Rd为11,Rn为19,shamt为4,Rm字段没有使用,被置为0。
逻辑左移还有额外的作用。左移i位相当于乘以2i,就像十进制数左移i位相当于乘以10i。例如,上面的LSL指令左移了4位,相当于乘以24或16。所以,例子中原二进制数表示的值是9,而9x16=144,恰好就是移位后的结果。
9位的地址字段意味若load register指令可以访问寄存器Rn所指基址前后±28或256个字节(±25或者32个双字)范围内的任意双字。这种格式下,很难使用32个以上的寄存器,因为Rn和Rt字段都必须增加额外的位。指令长度有限,我们没法把所有的东西都塞在一个字里。(D型指令的最后一个字段叫作Rt而不是Rd,因为对于存储指令,该字段指明的是源数据而不是目的数据。)
立即数指令如ADDI和SUBI等也需要相应的指令格式。尽管D型指令中的9位字段可以用来存放常量,但是ARMv8的设计者认为应该提供更大的字段存放立即数,甚至将操作码字段减少一位以构成12位的立即数。立即数指令或I型指令格式如下所示:
虽然多种指令格式增加了硬件的复杂性,但是保持指令格式的相似性在一定程度上可以降低复杂度。例如,三种指令格式的最后两个字段长度相同、名称相似、前两种指令格式的仅可操作码字段长度相同。
三种指令格式由第一个字段的值来区分:每种格式的第一个字段(opcode)都赋给不同的值,以便让计算机硬件知道如何处理指令的剩余部分。
和算术运算一样,逻揖操作中常数也非常有用。因此LEGv8也提供了立即数与(ANDI)、立即数或(ORRI),以及立即数异或(EORI)指令。
计算机与简单计算器的区别在于决策能力。根据输入的数据和计算过程中产生的值,计算机可以执行不同的指令。程序语言中通常使用if语句描述决策,有时也和 go to语句以及标签(lable)组合使用。LEGv8汇编语言中有两条决策指令,和 if 以及go to语句类似。第一条是:
CBZ register, L1
该指令表示:如果register的数值为0,则转到标签为L1的语句执行。助记符CBZ表示比较为0分支(compare and branch if zero)。
第二条指令是:
CBNZ register, L1
该指令表示:如果register的数值不为0,则转到标签为L1的语句执行。助记符CBNZ表示比较不为0分支(compare and branch if not zero)。这两条指令传统上称为条件分支(conditional branch)指令
,即CB型。
CB型指令的格式为:
opcode | address | rt |
---|---|---|
11bit | 16bit | 5bit |
在if语句的结尾部分,需要引人另一种分支指令,通常叫作无条件分支(unconditionalbranch)指令
,当遇到这种指令时,处理器必须跳转。为了区分条件分支和无条件分支,LEGv8将无条件分支指令命名为branch,简写成B(标签Exit将在后面定义),即B型。
B Exit // go to Exit
B型指令的格式为:
opcode | address |
---|---|
11bit | 21bit |
相等或不等是最常见的判断,但两数之间还有其他很多关系存在。例如,for循环需要判断索引变量是否小于0。其他比较操作还包括小于(<)、小于等于(S)、大于(>)、大于等于(之)、等于(=)和不等于(≠)。
比较位串的大小也必须区分有符号数和无符号数这两种情况。有符号数中,最高位为1的位串表示负数,比任何一个最高位为0的正数都小。而对于无符号数,最高位为1的数比任何最高位为0的数都大。(最高位的双重含义可以用来减少数组边界检查的开销。)
体系结构设计师很早之前就通过增加四个额外的二进制位来记录指令执行状态信息,从而解决上述问题。这些额外增加的位称为条件码(condition code)或标志位(flag)
:
条件分支指令通过组合使用这些条件码完成条件判断。在 LEGv8指令集中,这种条件分支指令是B.cond。cond可以用于任意有符号数
的比较指令中,如EQ(等于)、NE (不等于)、LT (小于)、LE(小于等于)、GT(大于)或GE(大于等于)
。cond 也可以用于无符号数
的比较指令,如Lo(低于)、LS(低于或相同)、HI(高于)或者HS(高于或相同)
。B.cond指令也是CB型指令,因此对于这种指令rt就是用来存条件码cond的,比如LE/GE等条件。假设设置条件码的指令是减法指令(A-B),则下图给出了LEGv8所有有符号数比较和无符号数比较操作对应的指今和条件码的值。
其实到这里为止,书里对条件码的设置讲解的不是很多,大家可能云里雾里的,不明白到底cond是怎么有效,又如何判断的。
事实上,在条件跳转语句B.cond之前一般都有一句标志位设置语句,比如:SUBS、ADDIS等,看下面的例子:
ADDI X10, XZR, #1 // X10 = 1
SUBIS XZR, X10, #1 // Judge X10 ?= 1
B.EQ L1 // if X10 == 1, then jump to L1
L1: // Label L1
首先给X10赋值立即数1(XZR的值恒为0),然后用X10中的值减去1,即1 - 1
,这时处理器内部会判断NZVC四个标志位的值(Z=1, N=0, V=0, C=0),根据图2.10(即上图)中的条件来进行判断(Z=1,则B.EQ成立),跳转到L1。
条件码的一个缺点是,如果许多指令频繁设置条件码,就可能造成依赖性问题,使得指令很难流水执行。因此,LEGv8规定只有少数指令–ADD、ADDI、AND、ANDI、SUB和SUBI——能设置条件码,并且条件码的设置是可选择的。在 LEGv8汇编语言中,如果想设置条件码,只需要在相应指令的尾部追加S,如ADDS、ADDIS、ANDS、ANDIS、SUBS和SUBIS。指令名称中实际上使用了术语“flag”,因此 ADDS的正确解释应该是“add and set flags”、即加并设置标志位。
LEGv8汇编语言为过程调用提供了一条指令:该指令在跳转到某个地址的同时,将下一条指令的地址保存在寄存器LR(X30)中。这条分支和链接指令(branch-and-link instruction)BL
可简单表示为:
BL ProcedureAddress
指令名中的链接代表指向调用者的地址或链接,以允许过程返回到合适
的地址。 存储在寄存器 LR 中的"链接”称为返回地址 (return address) 。
返回地址是必需的,因为同一过程可能在程序的不同地方被调用。
BL指令实际上将PC+4保存在寄存器LR中,从而链接到下一条指令的字节地址,为过程返回做好准备。
为了支持从过程调用返回,计算机(如LEGv8)使用了寄存器跳转(branch register)指令BR,表示无条件跳转到寄存器所指定的地址:
BR LR
寄存器跳转指令跳转到存储在LR寄存器中的地址——这正是用户所希望的。因此,调用程序或称为调用者(caller),将参数值放在X0~X7中,然后使用BL X跳转到过程X(有时称为被调用者(callee))。被调用者执行运算,将结果放在相同的参数寄存器中,然后通过BR LR指令将控制返回给调用者。
伪指令:所谓伪指令是汇编语言指令的一种常见的变形,经常被看作一条汇编指令。硬件不需要实现这些指令,然而它们在汇缩语言中的出现可以简化程序转换和编程的过程 。
本次实验中的伪指令有CMP和MUL两条伪指令。
LEGv8汇编器将比较指令CMP(compare)转换成一条减法指令,该减法指令用于设置条件码,并将XZR寄存器设置为目的寄存器。因此
CMP X9, Xl0 // compare X9 to X10 and set condition codes
转换成
SUBS XZR, X9, X10 // use X9 - X10 to set condition codes
对于MOV指令,LEGv8硬件确保寄存器XZR(X31)总为0,即任何时候使用寄存器XZR,其提供的值都是0,并且程序员不能修改寄存器XZR的值。寄存器XZR可以用于生成将一个寄存器中的内容复制到另一个中的汇编指令。因此,即使LEGv8机器语言中不存在下面这条指令,LEGv8汇编器也能够识别它:
MOV X9, X10 // register X9 gets register X10
汇编器将这条汇编语言指令转换成如下功能等价的机器语言:
ORR X9, XZR, X10 // register X9 gets 0 OR register X10
当然,MOV指令也可以通过一个常数操作数为0的加法来实现,例如:
ADD X9, X10, XZR // register X9 gets 0 ADD register X10
所以对于实现中出现的伪指令,只需通过编译器汇编成对应的真正的指令即可,无需硬件实现。
LEGv8中的所有指令操作码可以在《MK.Computer.Organization.and.Design.The.Hardware.Software.Interface.ARM.Edition》中倒数第二页附录中找到,这个表也是很有必要的,读者如果后续需要扩展其他指令,而又不知道操作码,则可以参考该表。由于图片太大无法全部截图,而且有些不清晰,建议看原pdf,这里就不放图了。
指令长度保持相同的需求与提供尽可能多的寄存器的需求是相互矛盾的。寄存器数量的增长会导致指令格式中各个寄存器字段至少增加1位。综合考虑这些限制压刀和越少越快的设计原则,当今大多数指令系统中都只有16个或32个通用寄存器。LEGv8中的32个寄存器用编号0~31表示。
存储器只能通过数据传输指令访问。LEGv8采用字节编址,所以连续的双字地址相差8。存储器存储数据结构、数组和溢出的寄存器。
寄存器是计算机中保存数据最快的地方,所以我们希望尽可能多地利用寄存器。LEGv8软件在为过程调用分配寄存器时遵循以下约定:
为了避免在过程中保存和恢复一个从未被使用过的寄存器(通常是临时寄存器),LEGv8软件将其中19个寄存器分为两组:
这一简单约定减少了寄存器的换出。
存储程序思想的一个隐含需求,是需要一个寄存器来保存当前正在执行的指令的地址。出于历史原因,这个寄存器通常称为程序计数器(program counter),LEGv8体系结构中缩写为PC。当然,这个寄存器更贴切的名字可能应该是指令地址寄存器。PC不属于32个寄存器中。
本次实验要自己动手写一个简单的汇编器,所以在这里首先简要介绍一下汇编器的基础知识。
汇编器的主要任务是汇编获得机器代码(二进制)。汇编器将汇编语言程序转换成目标文件,该文件包括机器语言指令、数据,以及将指令正确放入内存所需要的信息。
为了产生汇编语言程序中每条指令对应的二进制表示,汇编器必须确定所有标号对应的地址。汇编器将分支和数据传输指令中用到的标号都放人一个符号表(symbol table)中。正如你所想的,这个表包含了程序中出现的符号(标号)和地址对。
符号表:一个用来匹配标签名和指令所在的内存地址的列表。
在查看下面知识前,请查看目标文件相关知识。
UNIX 系统中的目标文件通常包含以下六个不同的部分:
目标文件头:描述目标文件其他部分的大小和位置。
代码段:包含机器语言代码。
静态数据段:包含在程序生命周期内分配的数据。(UNIX 系统允许程序使用存在于整个程序中的静态数据,也允许随程序的需要而增长或减少的动态数据。)
重定位信息:标记了一些在程序加载进内存时依赖于绝对地址的指令和数据。
符号表:包含剩余未定义的标签,如外部引用。
调试信息:包含一份简明描述,说明模块如何编译,以便调试器能够将机器指令关联到C源文件,并使数据结构也变得可读。
本次实验主要是实现两个程序:阶乘和冒泡排序算法,这两程序的C语言代码已经在第一篇博客中分析过了,具体请看:一个简单LEGv8处理器的Verilog实现【一】【实验简介】,这里主要分析一下汇编代码。为方便大家对比查看,这里把汇编代码和C代码都列出来。
C语言代码如下:
long long int fact(long long int n)
{
if(n < 1) return 1;
else return n * fact(n - 1);
}
LEGv8汇编代码如下(参数n在X0中,结果放在X1中):
fact:
SUBI SP, SP, #16 ; Save return address and n on stack
STUR LR, [SP, #8]
STUR X0, [SP, #0]
SUBIS XZR, X0, #1 ; compare n and 1
B.GE L1 ; if n >= 1, go to L1
ADDI X1, XZR, #1 ; Else, set return value to 1
ADDI SP, SP, #16 ; Pop stack, dont bother restoring values
BR LR ; Return: Copy LR to PC
L1:
SUBI X0, X0, #1 ; n = n - 1
BL fact ; call fact(n-1): put PC in LR(X30)
LDUR X0, [SP, #0] ; Restore caller's n
LDUR LR, [SP, #8] ; Restore caller's return address
ADDI SP, SP, #16 ; Pop stack
MUL X1, X0, X1 ; return n*fact(n-1)
BR LR ; return: Copy LR to PC
讲解:
以n=3为例:
首先进入到fact,将LR和X0中的值存到栈中(注意每次改变n进入fact的时候都会存一遍,所以SP在整个程序执行完后,不是仅仅只减了16,要看执行的n的大小,后面我们分析可实现的最大阶乘的时候会提到这个),一共两个数据,所以栈指针要减2*8=16。
然后通过SUBIS指令比较X0寄存器中的值(即n)与1的大小,如果大于等于则跳转到L1,否则继续执行。
此时n为3,大于1,则跳转到L1,则先让X0=X0-1,即n=n-1=2,然后继续跳转到fact执行,同时把下一条指令(LDUR X0, [SP, #0])的地址存到LR寄存器中。
重复上述过程,直到n=0。
此时n<1,则返回值设为1(阶乘最后一个数为1),然后将栈指针直接加回去。
然后会返回到上次LR中存的地址继续执行,其实就是BL fact后面的指令:LDUR X0, [SP, #0],恢复n=1时的X0和LR,此时X0=1,LR为n=2时的LDUR X0, [SP, #0]的地址。X1=X0 * X1=1 * 1=1。
然后从栈中取出来的X0=2,X1=X0 * X1=2 * 1=2。
再从栈中取出来X0=3,X1=X0 * X1=3 * 2=6,即最终结果为6。
swap函数C语言代码:
void swap(long long int v[], long long int k)
{
long long int temp;
temp = v[k];
v[k] = v[k+1];
v[k+1] = temp;
}
swap函数LEGv8汇编代码(v在X0中,k在X1中,temp在X9中):
swap:
LSL X10, X1, #3 ; reg X10 = k * 8
ADD X10, X0, X10 ; reg X10 = v + (k * 8)
; reg X10 has the address of v[k]
LDUR X9, [X10, #0] ; reg X9(tmp) = v[k]
LDUR X11, [X10, #8] ; reg X11 = v[k + 1]
; refers to next element of v
STUR X11, [X10, #0] ; v[k] = reg X11
STUR X9, [X10, #8] ; v[k + 1] = reg X9(tmp)
BR LR ; return to calling routine
进入到swap,首先将X1左移3位,即乘以8,X10 = X1 * 8,用作偏移地址;X0中存的是v的基地址,故X10 = X0+X10表示X10存的是v[k]的地址。
然后分别取出v[k]和v[k+1]的值,通过一个临时寄存器X11进行交换,最后返回到LR寄存器存的地址中。
sort函数C语言代码:
void sort(long long int v[], size_t n)
{
size_t i, j;
for(i = 0; i < n; i += 1)
{
for(j = i - 1; j >= 0 && v[j] > v[j+1]; j -= 1)
{
swap(v, j);
}
}
}
sort函数LEGv8汇编代码(v在X0中,n在X1中,i在X19中,j在X20中):
sort:
SUBI SP, SP, #40 ; make room on stack for 5 registers
STUR X30, [SP, #32] ; save LR on stack
STUR X22, [SP, #24] ; save X22 on stack
STUR X21, [SP, #16] ; save X21 on stack
STUR X20, [SP, #8] ; save X20 on stack
STUR X19, [SP, #0] ; save X19 on stack
MOV X21, X0 ; copy parameter X0 into X21
MOV X22, X1 ; copy parameter X1 into X22
MOV X19, XZR ; i = 0
for1tst:
CMP X19, X22 ; compare X19 to X22 (i to n)
B.GE exit1 ; go to exit1 if X19 >= X22 (i >= n)
SUBI X20, X19, #1 ; j = i - 1
for2tst:
CMP X20, XZR ; compare X20 to 0 (j to 0)
B.LT exit2 ; go to exit2 if X20 < 0 (j < 0)
LSL X10, X20, #3 ; reg X10 = j * 8
ADD X11, X0, X10 ; reg X11 = v + (j * 8)
LDUR X12, [X11, #0] ; reg X12 = v[j]
LDUR X13, [X11, #8] ; reg X13 = v[j + 1]
CMP X12, X13 ; compare X12 to X13
B.LE exit2 ; go to exit2 if X12 <= X13
MOV X0, X21 ; first swap parameter is v
MOV X1, X20 ; second swap parameter is j
BL swap ; branch to swap and link to X30
SUBI X20, X20, #1 ; j -= 1
B for2tst ; branch to test of inner loop( for2tst )
exit2:
ADDI X19, X19, #1 ; i += 1
B for1tst ; branch to test of outer loop( for1tst )
exit1:
LDUR X19, [SP, #0] ; restore X19 from stack
LDUR X20, [SP, #8] ; restore X20 from stack
LDUR X21, [SP, #16] ; restore X21 from stack
LDUR X22, [SP, #24] ; restore X22 from stack
LDUR X30, [SP, #32] ; restore X30 from stack
ADDI SP, SP, #40 ; restore stack pointer
BR LR ; return to calling routine
本来想自己讲解一下这个程序的,但是看了课本讲的很清晰,所以这里放弃自己写,copy一下课本文字,嘿嘿
前面几步用来保存寄存器的值:
sort:
SUBI SP, SP, #40 ; make room on stack for 5 registers
STUR X30, [SP, #32] ; save LR on stack
STUR X22, [SP, #24] ; save X22 on stack
STUR X21, [SP, #16] ; save X21 on stack
STUR X20, [SP, #8] ; save X20 on stack
STUR X19, [SP, #0] ; save X19 on stack
进入到sort函数,首先将X30、X22、X21、X20和X19存到栈中,前面讲寄存器时说过X19 ~X28是在过程调用中必须被保存(一旦被使用,由被调用者保存和恢复)的保存寄存器,所以这里需要提前将这些值保存到栈中。X30是LR寄存器,X21和X22是因为后面的代码有个MOV指令,需要将X0和X1分别存到X21和X22中,所以得提前把这两个寄存器中的值保存起来。X19和X20就比较好理解了,就是存的i和j。
MOV X21, X0 ; copy parameter X0 into X21
MOV X22, X1 ; copy parameter X1 into X22
保存到栈后,将X0的值和X1的值分别拷贝到X21和X22,这里不使用STUR压栈的方式进行存储是因为使用MOV可以进行快速拷贝,在过程执行的早期就将sort的参数复制到其他寄存器中,让出X0和X1寄存器供swap过程使用。这种复制要比使用栈进行保存和恢复快得多。
sort过程体包含两个嵌套的for循环和一个带参数的swap调用。下面由外向内来展开代码。
第一步翻译第一个for循环:
for( i = 0; i < n; i += 1 )
C语言中的for语句有三个部分:初始化、循环条件判断和迭代递增。将i初始化为0只需要一条指令,故for语句的第一部分为:
MOV X19, XZR // i = 0
将i递增同样也只需要一条指令实现,因此for语句的最后部分为:
ADDI X19, X19, #1 // i += 1
i从0开始;进入到for1tst,比较X19和X22的值,也即比较i和n的大小,如果i≥n,则跳转到exit1,退出循环。循环条件判断指令:
for1tst:
CMP X19, X22 ; compare X19 to X22 (i to n)
B.GE exit1 ; go to exit1 if X19 >= X22 (i >= n)
循环最后仅仅跳回循环条件判断的地方:
B for1tst //branch to test of outer loop
至此,第一个for循环的初始化、循环条件判断和迭代递增已经表示完成,还需要继续分析进入循环内部的代码。
第二个for循环的C语句如下:
for( j = i - 1; j >= 0 && v[j] > v[j+1]; j -= 1 )
这个循环的初始化部分仍然只需一条指令:
SUBI X20, X19, #1 // j = i - 1
循环末尾 j 的递减也只需一条指令实现:
SUBI X20, X20, #1 // j -= 1
循环条件测试由两部分组成,任何一个条件为假就退出循环 。 因此,如果第一个条件测试为假 (j<0),循环就要退出:
for2tst:
CMP X20, XZR ; compare X20 to 0 (j to 0)
B.LT exit2 ; go to exit2 if X20 < 0 (j < 0)
这条跳转指令将跳过第二个条件测试 。如果没有跳过,则j≥0。
如v[j]>v[j+1]非真,或v[j]≤v[j+1],则第二个测试退出。将j乘以8(我们需要字节地址),然后与v的基址相加:
LSL X10, X20, #3 // reg X10 = j * 8
ADD X11, X0, X10 // reg X11 = v + ( j * 8 )
现在取出v[j]:
LDUR X12, [X11, #0] // reg X12 =v[j]
因为第二个元素恰好是顺序下一个双字,因此将寄存器X11中的地址值加8就可以取出v[j+1]:
LDUR X13, [X11, #8] // reg X13 = v[j+1]
测试v[j]<=v[j+1],以判断是否跳出循环:
CMP X12, X13 // compare X12 to X13
B.LE exit2 // go to exit2 if X12 <= X13
循环末尾跳回到内层循环测试处:
B for2tst // branch to test of inner loop
将这些代码片段组合起来就可以得到第二个for循环的框架:
SUBI X20, X19, #1 // j = i - 1
for2tst:
CMP X20, XZR ; compare X20 to 0 (j to 0)
B.LT exit2 ; go to exit2 if X20 < 0 (j < 0)
LSL X10, X20, #3 ; reg X10 = j * 8
ADD X11, X0, X10 ; reg X11 = v + (j * 8)
LDUR X12, [X11, #0] ; reg X12 = v[j]
LDUR X13, [X11, #8] ; reg X13 = v[j + 1]
CMP X12, X13 ; compare X12 to X13
B.LE exit2 ; go to exit2 if X12 <= X13
...
(body of second for loop)
...
SUBI X20, X20, #1 ; j -= 1
B for2tst ; branch to test of inner loop( for2tst )
exit2:
下一步处理第二个for循环体:
swap(v, j);
调用swap足够简单(一条BL指令即可实现):
BL swap
sort中的参数传递
当我们想传递参数时问题出现了,因为sort过程需要使用寄存器X0和X1中的值,而swap过程需要将其参数放入同样的寄存器中。一种解决办法是在过程执行的早期就将sort的参数复制到其它寄存器中,让出X0和X1寄存器供swap过程使用。(这种复制要比使用栈进行保存和恢复快得多)过程中,首先将寄存器X0和X1的值如下方法复制到X21和X22中:
MOV X21, X0 // copy parameter X0 into X21
MOV X22, X1 // copy parameter X1 into X22
然后用下面两条指令将参数传递给swap:
MOV X0, X21 // first swap parameter is v
MOV X1, X20 // second swap parameter is j
在sort中保存寄存器
剩下的代码保存和恢复寄存器值。显然,我们必须将返回地址保存在寄存器LR中,因为sort是一个过程并且本身也被调用。sort过程还是用了由被调用者保存的寄存器X19、X20、X21和X22,这些寄存器的值也必须被保存。因此,sort过程开头的代码如下:
SUBI SP, SP, #40 ; make room on stack for 5 registers
STUR X30, [SP, #32] ; save LR on stack
STUR X22, [SP, #24] ; save X22 on stack
STUR X21, [SP, #16] ; save X21 on stack
STUR X20, [SP, #8] ; save X20 on stack
STUR X19, [SP, #0] ; save X19 on stack
过程末尾只需要简单地反向执行这些指令,最后加入一条BR指令以实现返回。
完成的sort过程
现将上述所有代码片段组合起来,注意for循环中对寄存器X0和X1的引用被替换成了对寄存器X21和X22的引用。
注:中文翻译版书中代码有些错误
前面已经把X1的值拷贝到X22里面了,所以这里应该是比较X19和X22的值。
这里所有的STUR都要改成LDUR,并且最后一个SUBI要改成ADDI。
好的,本节就先讲到这里,如果你有收获且乐意的话,麻烦点个赞哦,收藏也可以哇( ̄▽ ̄)~*
本系列博客共分为5篇:
第一篇对一些处理器基础知识进行简单讲解,并讲清楚实验要求。
第二篇从指令、寄存器、汇编器的角度对设计处理器所需要的基础知识进行较为详细的讲解,并对实验所需汇编程序进行了分析。
第三篇讲解数字IC设计所需要的一些工具以及使用方法,并讲解了一些编程规范。
第四篇主要是针对单周期CPU的基础理论和模块设计进行讲解,在讲Verilog实现的时候,顺带讲了很多编程规范。
lijyhh/LEGv8
链接:https://pan.baidu.com/s/1dtpFTsJ5fdEmnPDyX66U1Q
提取码:b26b