Hexagon 处理器支持以下程序流程工具:
许多 Hexagon 处理器指令可以有条件地执行。 例如:
if (P0) R0 = memw(R2) // conditionally load word if P0
if (!P1) jump label // conditionally jump if not P1
以下指令可以指定为有条件的:
Hexagon 处理器包含硬件循环指令,可以执行零开销的循环分支。 例如:
loop0(start,#3) // loop 3 times
start:
{ R0 = mpyi(R0,R0) } :endloop0
提供了两组硬件循环指令 loop0 和loop1–以使硬件循环可以嵌套一层。 例如:
// Sum the rows of a 100x200 matrix.
loop1(outer_start,#100)
outer_start:
R0 = #0
loop0(inner_start,#200)
inner_start:
R3 = memw(R1++#4)
{ R0 = add(R0,R3) }:endloop0
{ memw(R2++#4) = R0 }:endloop1
硬件循环指令使用如下:
注意 如果程序需要创建嵌套超过一层的循环,则最内层的两个循环可以实现为硬件循环,其余的外层循环实现为软件分支。
每个硬件循环都与一对专用循环寄存器相关联:
硬件循环设置指令一次设置这两个寄存器——通常不需要单独设置它们。 然而,由于循环寄存器完全指定了硬件循环状态,它们可以被保存和恢复(由处理器中断自动或由程序员手动),一旦它的循环寄存器被重新加载,就可以正常恢复暂停的硬件循环。保存的值。
Hexagon 处理器为两个硬件循环提供了两组循环寄存器:
表 7-1 列出了硬件循环指令。
语法 | 描述 |
---|---|
loopN(start, Rs) | 具有寄存器循环计数的硬件循环。 为硬件循环 N 设置寄存器 SAn 和 LCn: * SAn 被分配了指定的循环起始地址。 * LCn 被赋值为通用寄存器Rs 的值。 注意 - 循环开始操作数被编码为 PC 相关的立即数。 |
loopN(start, #count) | 具有即时循环计数的硬件循环。 为硬件循环 N 设置寄存器 SAn 和 LCn: SAn 被分配了指定的循环起始地址。 LCn 被赋予指定的立即数 (0-1023)。 注意 - 循环开始操作数被编码为 PC 相关的立即数。 |
:endloopN | 硬件循环结束指令。 执行以下操作:if (LCn > 1) {PC = SAn; LCn = LCn-1} 注意:此指令在汇编中显示为附加到循环中最后一个数据包的后缀。 它被编码在最后一个数据包中。 |
SAn = Rs | 将循环起始地址设置为通用寄存器 Rs |
LCn = Rs | 将循环计数设置为通用寄存器 Rs |
注意
循环指令分配给指令类 CR。
要设置硬件循环,必须将循环寄存器 SAn 和 LCn 设置为正确的值。
这可以通过两种方式完成:
loopN 指令执行设置 SAn 和 LCn 的所有工作。 例如:
loop0(start,#3) // SA0=&start, LC0=3
start:
{ R0 = mpyi(R0,R0) } :endloop0
在本例中,硬件循环(由一条乘法指令组成)执行了 3 次。 loop0 指令将寄存器 SA0 设置为标签起始的地址值,并将 LC0 设置为 3。
当循环计数在 loopN 中表示为立即数时,循环计数被限制在 0-1023 的范围内。 如果所需的循环计数超出此范围,则必须将其指定为寄存器值。 例如:
使用循环N:
R1 = #20000;
loop0(start,R1) // LC0=20000, SA0=&start
start:
{ R0 = mpyi(R0,R0) } :endloop0
使用寄存器赋值:
R1 = #20000
LC0 = R1 // LC0=20000
R1 = #start
SA0 = R1 // SA0=&start
start:
{ R0 = mpyi(R0,R0) } :endloop0
如果 loopN 指令距离其循环起始地址太远,则用于指定起始地址的 PC 相对偏移值可能会超出指令起始地址操作数的最大范围。 如果发生这种情况,要么将 loopN 指令移近循环开始,要么将循环开始地址指定为 32 位常量(第 10.9 节)。 例如:
使用 32 位常量:
R1 = #20000;
loop0(##start,R1) // LC0=20000, SA0=&start
...
循环结束指令指示硬件循环中的最后一个数据包。 它用汇编语言表示,在数据包后面加上符号":endloopN",其中 N 指定硬件循环(0 或 1)。 例如:
loop0(start,#3)
start:
{ R0 = mpyi(R0,R0) } :endloop0 // last packet in loop
循环中的最后一条指令必须始终用汇编语言表示为一个数据包(使用花括号),即使它是数据包中的唯一指令。
嵌套的硬件循环可以指定相同的指令作为内部和外部循环的结束。 例如:
// Sum the rows of a 100x200 matrix.
// Software pipeline the outer loop.
p0 = cmp.gt(R0,R0) // p0 = false
loop1(outer_start,#100)
outer_start:
{ if (p0) memw(R2++#4) = R0
p0 = cmp.eq(R0,R0) // p0 = true
R0 = #0
loop0(inner_start,#200) }
inner_start:
R3 = memw(R1++#4)
{ R0 = add(R0,R3) }:endloop0:endloop1
memw(R2++#4) = R0
虽然 endloopN 的行为类似于常规指令(通过实现循环测试和分支),但请注意它不会在任何指令槽中执行,并且不计为数据包中的指令。 因此,标记为循环结束的单个指令包最多可以执行六个操作:
注意 endloopN 指令被编码在指令包中(第 10.6 节)。
建立硬件循环后,无论指定的循环计数如何,循环体总是至少执行一次(因为直到循环中的最后一条指令才检查循环计数)。 因此,如果一个循环需要可选地执行零次,则必须在它之前有一个显式的条件分支。 例如:
loop0(start,R1)
P0 = cmp.eq(R1,#0)
if (P0) jump skip
start:
{ R0 = mpyi(R0,R0) } :endloop0
skip:
在此示例中,使用 R1 中的循环计数设置了一个硬件循环,但如果 R1 中的值为零,则软件分支将跳过循环体。
执行完硬件循环的循环结束指令后,Hexagon 处理器会检查相应循环计数寄存器中的值:
注意 因为嵌套的硬件循环可以共享相同的循环结束指令,处理器可以在一次操作中检查两个循环计数寄存器。
软件流水线循环对于 Hexagon 处理器等 VLIW 架构很常见。 它们通过重叠多个循环迭代来提高循环中的代码性能。
软件流水线包含三个部分:
int foo(int *A, int *result)
{
int i;
for (i=0;i<100;i++) {
result[i]= A[i]*A[i];
}
}
foo:
{ R3 = R1
loop0(.kernel,#98) // Decrease loop count by 2
}
R1 = memw(R0++#4) // 1st prologue stage
{ R1 = memw(R0++#4) // 2nd prologue stage
R2 = mpyi(R1,R1)
}
.falign
.kernel:
{ R1 = memw(R0++#4) // kernel
R2 = mpyi(R1,R1)
memw(R3++#4) = R2
}:endloop0
{ R2 = mpyi(R1,R1) // 1st epilogue stage
memw(R3++#4) = R2
}
memw(R3++#4) = R2 // 2nd epilogue stage
jumpr lr
上述代码中,流水线循环的内核部分并行执行循环的三个迭代:
软件流水线的一个缺点是流水线循环的序言和结尾部分所需的额外代码。
为了解决这个问题,Hexagon 处理器提供了 spNloop0 指令,其中指令名称中的“N”表示 1-3 范围内的数字。 例如:
P3 = sp2loop0(start,#10) // Set up pipelined loop
spNloop0 是 loop0 指令的变体:它使用 SA0 和 LC0 建立一个正常的硬件循环,但还执行以下附加操作:
此功能(称为自动条件控制)使流水线循环的内核部分中的存储指令能够由 P3 有条件地执行,因此由于 spNloop0 控制 P3 的方式 - 在流水线预热期间不会执行。 这可以通过消除对序言代码的需要来减少许多软件流水线循环的代码大小。
spNloop0 不能用于从流水线循环中消除结尾代码; 但是,在某些情况下,可以通过使用编程技术来做到这一点。
通常,影响结尾代码删除的问题是加载安全性。 如果流水线循环的内核部分可以安全地访问其数组的末尾——要么是因为已知它是安全的,要么是因为数组已在末尾填充——那么结尾代码是不必要的。 但是,如果无法确保加载安全,则需要显式的结尾代码来排空软件管道。
软件流水线循环(使用 spNloop0)
int foo(int *A, int *result)
{
int i;
for (i=0;i<100;i++) {
result[i]= A[i]*A[i];
}
}
foo:
{ // load safety assumed
P3 = sp2loop0(.kernel,#102) // set up pipelined loop
R3 = R1
}
.falign
.kernel:
{ R1 = memw(R0++#4) // kernel
R2 = mpyi(R1,R1)
if (P3) memw(R3++#4) = R2
}:endloop0
jumpr lr
注意 spNloop0 用来控制 P3 设置的计数值存储在用户状态寄存器 USR.LPCFG 中。
硬件循环有以下限制:
注意 SA1 和 LC1 可以在 loop0 结束时更改,而 SA0 和 LC0 可以在 loop1 结束时更改。