• Hexagon_V65_Programmers_Reference_Manual(8)


    7. 程序流程

    Hexagon 处理器支持以下程序流程工具:

    • 条件指令
    • 硬件循环
    • 软件分支
    • 暂停
    • 异常
      软件分支包括跳转、调用和返回。 支持几种类型的跳转:
    • 条件跳转(Speculative jumps)
    • 比较跳转(Compare jumps)
    • 寄存器赋值跳转(Register transfer jumps)
    • 双跳(Dual jumps)

    7.1 条件指令

    许多 Hexagon 处理器指令可以有条件地执行。 例如:

    if (P0) R0 = memw(R2) // conditionally load word if P0
    if (!P1) jump label // conditionally jump if not P1
    
    • 1
    • 2

    以下指令可以指定为有条件的:

    • 跳转和调用
    • 许多加载和存储指令
    • 逻辑指令(包括AND/OR/XOR)
    • 移位半字
    • 通过寄存器或短立即数进行 32 位加/减
    • 符号和零扩展
    • 32 位寄存器传输和 64 位组合字
    • 寄存器立即赋值(Register transfer immediate)
    • 释放帧并返回

    7.2 硬件循环

    Hexagon 处理器包含硬件循环指令,可以执行零开销的循环分支。 例如:

        loop0(start,#3) // loop 3 times
    start:
        { R0 = mpyi(R0,R0) } :endloop0
    
    • 1
    • 2
    • 3

    提供了两组硬件循环指令 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    硬件循环指令使用如下:

    • 对于非嵌套循环,使用loop0。
    • 对于嵌套循环,loop0 用于内循环,loop1 用于外循环。
    注意 如果程序需要创建嵌套超过一层的循环,则最内层的两个循环可以实现为硬件循环,其余的外层循环实现为软件分支。
    
    • 1

    每个硬件循环都与一对专用循环寄存器相关联:

    • 循环起始地址寄存器 SAn 设置为循环中第一条指令的地址(通常用汇编语言表示为标签)。
    • 循环计数寄存器 LCn 设置为 32 位无符号值,指定要执行的循环迭代次数。 当 PC 到达循环结束时,检查 LCn 以确定循环是否应该重复或退出。

    硬件循环设置指令一次设置这两个寄存器——通常不需要单独设置它们。 然而,由于循环寄存器完全指定了硬件循环状态,它们可以被保存和恢复(由处理器中断自动或由程序员手动),一旦它的循环寄存器被重新加载,就可以正常恢复暂停的硬件循环。保存的值。
    Hexagon 处理器为两个硬件循环提供了两组循环寄存器:

    • SA0 和 LC0 被 loop0 使用
    • SA1 和 LC1 被 loop1 使用

    表 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。
    
    • 1
    • 2

    7.2.1 循环设置

    要设置硬件循环,必须将循环寄存器 SAn 和 LCn 设置为正确的值。
    这可以通过两种方式完成:

    • loopN 指令
    • 寄存器传输到 SAn 和 LCn

    loopN 指令执行设置 SAn 和 LCn 的所有工作。 例如:

        loop0(start,#3) // SA0=&start, LC0=3
    start:
        { R0 = mpyi(R0,R0) } :endloop0
    
    • 1
    • 2
    • 3

    在本例中,硬件循环(由一条乘法指令组成)执行了 3 次。 loop0 指令将寄存器 SA0 设置为标签起始的地址值,并将 LC0 设置为 3。

    当循环计数在 loopN 中表示为立即数时,循环计数被限制在 0-1023 的范围内。 如果所需的循环计数超出此范围,则必须将其指定为寄存器值。 例如:

    使用循环N:

        R1 = #20000;
        loop0(start,R1) // LC0=20000, SA0=&start
    start:
        { R0 = mpyi(R0,R0) } :endloop0
    
    • 1
    • 2
    • 3
    • 4

    使用寄存器赋值:

        R1 = #20000
        LC0 = R1 // LC0=20000
        R1 = #start
        SA0 = R1 // SA0=&start
    start:
        { R0 = mpyi(R0,R0) } :endloop0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    如果 loopN 指令距离其循环起始地址太远,则用于指定起始地址的 PC 相对偏移值可能会超出指令起始地址操作数的最大范围。 如果发生这种情况,要么将 loopN 指令移近循环开始,要么将循环开始地址指定为 32 位常量(第 10.9 节)。 例如:
    使用 32 位常量:

        R1 = #20000;
        loop0(##start,R1) // LC0=20000, SA0=&start
        ...
    
    • 1
    • 2
    • 3

    7.2.2 循环结束

    循环结束指令指示硬件循环中的最后一个数据包。 它用汇编语言表示,在数据包后面加上符号":endloopN",其中 N 指定硬件循环(0 或 1)。 例如:

        loop0(start,#3)
    start:
        { R0 = mpyi(R0,R0) } :endloop0 // last packet in loop
    
    • 1
    • 2
    • 3

    循环中的最后一条指令必须始终用汇编语言表示为一个数据包(使用花括号),即使它是数据包中的唯一指令。

    嵌套的硬件循环可以指定相同的指令作为内部和外部循环的结束。 例如:

    // 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    虽然 endloopN 的行为类似于常规指令(通过实现循环测试和分支),但请注意它不会在任何指令槽中执行,并且不计为数据包中的指令。 因此,标记为循环结束的单个指令包最多可以执行六个操作:

    • 四个常规指令(一个指令包的正常限制)
    • endloop0 测试和分支
    • endloop1 测试和分支
    注意 endloopN 指令被编码在指令包中(第 10.6 节)。
    
    • 1

    7.2.3 循环执行

    建立硬件循环后,无论指定的循环计数如何,循环体总是至少执行一次(因为直到循环中的最后一条指令才检查循环计数)。 因此,如果一个循环需要可选地执行零次,则必须在它之前有一个显式的条件分支。 例如:

        loop0(start,R1)
        P0 = cmp.eq(R1,#0)
        if (P0) jump skip
    start:
        { R0 = mpyi(R0,R0) } :endloop0
    skip:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在此示例中,使用 R1 中的循环计数设置了一个硬件循环,但如果 R​​1 中的值为零,则软件分支将跳过循环体。

    执行完硬件循环的循环结束指令后,Hexagon 处理器会检查相应循环计数寄存器中的值:

    • 如果该值大于 1,则处理器递减循环计数寄存器并执行零循环分支到循环起始地址。
    • 如果该值小于或等于 1,则处理器在紧跟循环结束指令之后的指令处恢复程序执行。
    注意 因为嵌套的硬件循环可以共享相同的循环结束指令,处理器可以在一次操作中检查两个循环计数寄存器。
    
    • 1

    7.2.4 流水线硬件循环

    软件流水线循环对于 Hexagon 处理器等 VLIW 架构很常见。 它们通过重叠多个循环迭代来提高循环中的代码性能。

    软件流水线包含三个部分:

    • 循环开始的序幕
    • 内核(或稳态)部分
    • 流水线耗尽的尾声
      最好用一个简单的例子来说明这一点,如下所示。
    int foo(int *A, int *result)
    {
        int i;
        for (i=0;i<100;i++) {
            result[i]= A[i]*A[i];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    上述代码中,流水线循环的内核部分并行执行循环的三个迭代:

    • 迭代 N+2 的负载
    • 迭代 N+1 的乘法
    • 迭代 N 的存储

    软件流水线的一个缺点是流水线循环的序言和结尾部分所需的额外代码。
    为了解决这个问题,Hexagon 处理器提供了 spNloop0 指令,其中指令名称中的“N”表示 1-3 范围内的数字。 例如:

        P3 = sp2loop0(start,#10) // Set up pipelined loop
    
    • 1

    spNloop0 是 loop0 指令的变体:它使用 SA0 和 LC0 建立一个正常的硬件循环,但还执行以下附加操作:

    • 执行spNloop0指令时,将真值false赋给条件寄存器P3。
    • 关联循环执行 N 次后,P3 自动设置为 true。

    此功能(称为自动条件控制)使流水线循环的内核部分中的存储指令能够由 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];
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    注意 spNloop0 用来控制 P3 设置的计数值存储在用户状态寄存器 USR.LPCFG 中。
    
    • 1

    7.2.5 循环限制

    硬件循环有以下限制:

    • loopN 或 spNloop0(第 7.2.4 节)中的循环设置数据包不能包含推测性间接跳转、新值比较跳转或 dealloc_return。
    • 硬件循环中的最后一个数据包不能包含任何程序流指令(包括跳转或调用)。
    • loop0 中的循环结束包不能包含任何改变SA0 或LC0 的指令。 同样,loop1 中的循环结束包不能包含任何改变 SA1 或 LC1 的指令。
    • spNloop0中的循环结束包不能包含任何改变P3的指令。
    注意 SA1 和 LC1 可以在 loop0 结束时更改,而 SA0 和 LC0 可以在 loop1 结束时更改。
    
    • 1
  • 相关阅读:
    SpringBoot 整合多数据源
    在unity 2020 urp版本中实现 LensFlare功能
    2023 年高教社杯全国大学生数学建模竞赛获奖名单(初稿)公示
    毛玻璃态卡片悬停效果
    硬件科普系列之显示篇——LCD与OLED知多少
    动态规划示例理解
    自制操作系统日记(8):变量显示
    5G vs 4G
    R语言与作物模型(以DSSAT模型为例)融合应用
    【Linux:进程概念】
  • 原文地址:https://blog.csdn.net/weixin_38498942/article/details/125909063