我是汇编语言的新手。我正在阅读 MIPS 架构,我坚持使用跳转目标地址和分支目标地址和如何计算每个他们。
答案 0 :(得分:64)
在MIPS分支指令中只有16位偏移量来确定下一条指令。我们需要在此16位值中添加一个寄存器来确定下一条指令,该寄存器实际上是由架构暗示的。它是PC寄存器,因为在获取周期期间PC被更新(PC + 4),因此它保存下一条指令的地址。
(在下图中,PC
是分支延迟槽的地址,而不是分支指令本身。但在文中,我们将说PC + 4。)
我们还将分支距离限制为来自(后面的指令)分支指令的-2^15 to +2^15 - 1
指令。然而,这不是真正的问题,因为大多数分支机构都是本地的。
一步一步:
对于跳转指令,Mips只有26位来确定跳转位置。此外,跳转与MIPS中的PC相关。像分支一样,立即跳转值需要字对齐;因此,我们需要将26位地址乘以4。
一步一步:
换句话说,用向前移位2位的取指令的低26位替换PC + 4的低28位。
跳转是相对于分支延迟槽的区域,不一定是分支本身。在上图中,PC在跳转计算之前已经进入分支延迟槽。 (在经典RISC 5阶段流水线中,BD在同一周期内被提取,跳转被解码,因此PC + 4下一个指令地址已经可用于跳转和分支,并且相对于跳转自己的地址计算需要额外的工作来保存该地址。)
来源: Bilkent大学CS 224课程幻灯片
答案 1 :(得分:18)
通常您不必担心计算它们,因为汇编程序(或链接程序)将采取正确的计算方法。假设你有一个小功能:
func:
slti $t0, $a0, 2
beq $t0, $zero, cont
ori $v0, $zero, 1
jr $ra
cont:
...
jal func
...
当将上述代码转换为二进制指令流时,汇编程序(或链接器,如果你第一次组装成目标文件)将确定函数将驻留在内存中的哪个位置(让我们暂时忽略与位置无关的代码)。它在内存中的位置通常在ABI中指定,或者如果您使用的是模拟器(如SPIM加载0x400000
处的代码,则会提供给您 - 请注意该链接还包含一个很好的解释过程)。
假设我们正在谈论SPIM案例,而我们的函数首先在内存中,slti
指令将驻留在0x400000
,beq
位于0x400004
,所以上。现在我们快到了!对于beq
指令,分支目标地址是cont
(0x400010
)查看MIPS instruction reference的指令,我们看到它被编码为相对于下一条指令,16位立即签名(除以4,因为所有指令必须始终位于4字节对齐的地址上)。
那是:
Current address of instruction + 4 = 0x400004 + 4 = 0x400008
Branch target = 0x400010
Difference = 0x400010 - 0x400008 = 0x8
To encode = Difference / 4 = 0x8 / 4 = 0x2 = 0b10
beq $t0, $zero, cont
0001 00ss ssst tttt iiii iiii iiii iiii
---------------------------------------
0001 0001 0000 0000 0000 0000 0000 0010
如您所见,您可以分支到-0x1fffc .. 0x20000
个字节内。如果由于某种原因,你需要进一步跳跃,你可以使用蹦床(无条件跳转到放置在给定限制内的真实目标)。
跳转目标地址使用绝对地址进行编码(再次除以4)。由于指令编码使用6位作为操作码,因此只留下26位用于地址(实际上28位,因为最后2位将为0)因此在形成地址时使用PC寄存器的4位最高有效位(除非你打算跳过256 MB边界,否则无关紧要。)
回到上面的例子,jal func
的编码是:
Destination address = absolute address of func = 0x400000
Divided by 4 = 0x400000 / 4 = 0x100000
Lower 26 bits = 0x100000 & 0x03ffffff = 0x100000 = 0b100000000000000000000
0000 11ii iiii iiii iiii iiii iiii iiii
---------------------------------------
0000 1100 0001 0000 0000 0000 0000 0000
您可以快速验证这一点,并使用此online MIPS assembler运行不同的指令(注意它不支持所有操作码,例如slti
,所以我只是将其更改为slt
这里):
00400000: <func> ; <input:0> func:
00400000: 0000002a ; <input:1> slt $t0, $a0, 2
00400004: 11000002 ; <input:2> beq $t0, $zero, cont
00400008: 34020001 ; <input:3> ori $v0, $zero, 1
0040000c: 03e00008 ; <input:4> jr $ra
00400010: <cont> ; <input:5> cont:
00400010: 0c100000 ; <input:7> jal func
答案 2 :(得分:0)
对于像这样的小函数,您可以从分支指令下的指令手动计算它对目标的跳数。如果它向后分支使得跳数为负数。如果该数字不需要全部16位,那么对于最重要的跳数左边的每个数字,将它们设为1,如果跳数为正,则将它们全部设为0,因为大多数分支都接近它们目标,这为大多数情况节省了大量额外的算术。
答案 3 :(得分:-1)
我认为计算这些是非常困难的,因为分支目标地址是在运行时确定的,并且预测是在硬件中完成的。如果你更深入地解释了这个问题,并描述了你想要做的事情,那将会更容易帮助。 (: