ESP32-S2 ULP 协处理器指令

[English]

本文档详细介绍了 ESP32-S2 ULP FSM 协处理器汇编程序使用的指令。

ULP FSM 协处理器有 4 个 16 位通用寄存器,分别标记为 R0、R1、R2、R3,还有一个 8 位计数器寄存器 (stage_cnt) 用来实现循环。可以用特殊指令来访问阶段计数寄存器。

ULP 协处理器可以访问 8 K 字节大小的 RTC_SLOW_MEM 内存区域。内存以 32 位字单位寻址。它还可以访问 RTC_CNTLRTC_IOSENS 外设中的外设寄存器。

所有指令都是 32 位。跳转指令、ALU 指令、外设寄存器和内存访问指令在 1 个周期内执行。与外设(TSENS、ADC 和 I2C)相关的指令所需的周期不同,具体取决于外设操作。

指令语法不区分大小写。无论是寄存器名称还是指令名称,都可以任意混合使用大小写字母。

寻址注意事项

对于 ESP32-S2 ULP FSM 协处理器的 JUMPSTLD 系列指令,地址参数应以如下方式表示(具体取决于使用的地址参数类型):

  • 当地址参数作为标签时,指令中的地址应为 32 位字。

    对于示例程序:

    entry:
            NOP
            NOP
            NOP
            NOP
    loop:
            MOVE R1, loop
            JUMP R1
    

    当此程序被汇编和链接时,标签 loop 的地址将为 16 字节。然而 JUMP 指令期望寄存器 R1 中存储的地址以 32 位字表示。由于这种情况较为常见,汇编程序会在生成 MOVE 指令时将标签 loop 的地址从字节转换为字。因此,生成的代码相当于:

    0000    NOP
    0004    NOP
    0008    NOP
    000c    NOP
    0010    MOVE R1, 4
    0014    JUMP R1
    
  • 另一种情况是,MOVE 指令的参数不是标签,而是常量。此时汇编程序将 直接使用该常量 ,不进行任何转换:

    .set        val, 0x10
    MOVE        R1, val
    

    在这种情况下,加载到 R1 的值为 0x10

    但是,当使用立即数作为 LDST 指令的偏移量时,汇编程序会认为地址参数是字节,并在执行指令前将其转换为 32 位字:

    ST R1, R2, 4        // offset = 4 bytes; Mem[R2 + 4 / 4] = R1
    

    在这种情况下, R1 中的值存储在 [R2 + offset / 4] 指向的内存位置。

    请看以下代码:

            .global array
    array:  .long 0
            .long 0
            .long 0
            .long 0
    
            MOVE R1, array
            MOVE R2, 0x1234
            ST R2, R1, 0      // 将 R2 的值写入第一个数组元素,
                              // 即 array[0]
    
            ST R2, R1, 4      // 将 R2 的值写入第二个数组元素,
                              //(4 字节偏移量),即 array[1]
    
            ADD R1, R1, 2     // 将地址递增 2 个字(8 个字节)
            ST R2, R1, 0      // 将 R2 的值写入第三个数组元素,
                              // 即 array[2]
    

指令执行时间注意事项

ULP 协处理器的时钟 RTC_FAST_CLK 通常来自内部的 8 MHz 振荡器。如果应用程序需要获知精确 ULP 时钟频率,可以根据主 XTAL 时钟进行校准:

#include "soc/rtc.h"

// calibrate 8M/256 clock against XTAL, get 8M/256 clock period
uint32_t rtc_8md256_period = rtc_clk_cal(RTC_CAL_8MD256, 100);
uint32_t rtc_fast_freq_hz = 1000000ULL * (1 << RTC_CLK_CAL_FRACT) * 256 / rtc_8md256_period;

ULP 协处理器在获取每个指令时需要一定的时钟周期,执行时同样需要一定的时钟周期,此周期数取决于具体的指令。下文详细列出了每个指令所需的执行时间信息。

指令获取时间:

  • 2 个时钟周期 - ALU 和分支类的指令

  • 4 个时钟周期 - 其他指令

注意,访问 RTC 存储器和 RTC 寄存器时,ULP 协处理器的优先级低于主 CPU。这意味着当主 CPU 与 ULP 访问同一块内存区域时,ULP 协处理器需要等待,主 CPU 会优先访问。

以下是所有指令的详细描述:

ESP32 ULP 和 ESP32-S2 ULP 指令集的区别

与 ESP32 ULP FSM 协处理器相比,ESP32-S2 ULP FSM 协处理器具有扩展的指令集。ESP32-S2 ULP FSM 与 ESP32 ULP FSM 二进制不兼容,但在重新构建后,ESP32 ULP FSM 的汇编程序应能在 ESP32-S2 ULP FSM 上运行。

添加到 ESP32-S2 ULP FSM 的新指令包括:LDLLDHSTLSTHST32STOSTISTI32

NOP - 无操作

语法

NOP

操作数

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

不执行任何操作,只增加 PC

示例:

1:    NOP

ADD - 做加法运算

语法

ADD Rdst, Rsrc1, Rsrc2

ADD Rdst, Rsrc1, imm

操作数

  • Rdst - 寄存器 R[0..3]

  • Rsrc1 - 寄存器 R[0..3]

  • Rsrc2 - 寄存器 R[0..3]

  • Imm - 16 位有符号值

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

该指令将源寄存器与另一个源寄存器中的值相加或与一个 16 位有符号值相加,并将结果存储在目标寄存器中。

示例:

1:    ADD R1, R2, R3        // R1 = R2 + R3

2:    Add R1, R2, 0x1234    // R1 = R2 + 0x1234

3:    .set value1, 0x03     // constant value1=0x03
      Add R1, R2, value1    // R1 = R2 + value1

4:    .global label         // declaration of variable label
      add R1, R2, label     // R1 = R2 + label
        ...
      label: nop            // definition of variable label

SUB - 做减法运算

语法

SUB Rdst, Rsrc1, Rsrc2

SUB Rdst, Rsrc1, imm

操作数

  • Rdst - 寄存器 R[0..3]

  • Rsrc1 - 寄存器 R[0..3]

  • Rsrc2 - 寄存器 R[0..3]

  • Imm - 16 位有符号值

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

该指令将两个源寄存器中的值相减,或从一个源寄存器中减去一个 16 位有符号值,并将结果存储到目标寄存器中。

示例:

1:         SUB R1, R2, R3             // R1 = R2 - R3

2:         sub R1, R2, 0x1234         // R1 = R2 - 0x1234

3:         .set value1, 0x03          // constant value1=0x03
           SUB R1, R2, value1         // R1 = R2 - value1
4:         .global label              // declaration of variable label
           SUB R1, R2, label          // R1 = R2 - label
             ....
  label:   nop                        // definition of variable label

AND - 两个操作数的按位与

语法

AND Rdst, Rsrc1, Rsrc2

AND Rdst, Rsrc1, imm

操作数

  • Rdst - 寄存器 R[0..3]

  • Rsrc1 - 寄存器 R[0..3]

  • Rsrc2 - 寄存器 R[0..3]

  • Imm - 16 位有符号值

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

该指令对源寄存器的值和另一个源寄存器的值或一个 16 位有符号值进行按位与操作,并将结果存储到目标寄存器中。

示例:

1:        AND R1, R2, R3          // R1 = R2 & R3

2:        AND R1, R2, 0x1234      // R1 = R2 & 0x1234

3:        .set value1, 0x03       // constant value1=0x03
          AND R1, R2, value1      // R1 = R2 & value1

4:        .global label           // declaration of variable label
          AND R1, R2, label       // R1 = R2 & label
              ...
  label:  nop                     // definition of variable label

OR - 两个操作数的按位或

语法

OR Rdst, Rsrc1, Rsrc2

OR Rdst, Rsrc1, imm

操作数

  • Rdst - 寄存器 R[0..3]

  • Rsrc1 - 寄存器 R[0..3]

  • Rsrc2 - 寄存器 R[0..3]

  • Imm - 16 位有符号值

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

该指令对源寄存器的值和另一个源寄存器的值或一个 16 位有符号值进行按位或操作,并将结果存储到目标寄存器中。

示例:

1:       OR R1, R2, R3           // R1 = R2 || R3

2:       OR R1, R2, 0x1234       // R1 = R2 || 0x1234

3:       .set value1, 0x03       // constant value1=0x03
         OR R1, R2, value1       // R1 = R2 || value1

4:       .global label           // declaration of variable label
         OR R1, R2, label        // R1 = R2 || label
         ...
  label: nop                     // definition of variable label

LSH - 逻辑左移

语法

LSH Rdst, Rsrc1, Rsrc2

LSH Rdst, Rsrc1, imm

操作数

  • Rdst - 寄存器 R[0..3]

  • Rsrc1 - 寄存器 R[0..3]

  • Rsrc2 - 寄存器 R[0..3]

  • Imm - 16 位有符号值

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

该指令对源寄存器进行逻辑左移,移动的位数由另一个源寄存器或一个16位有符号值确定,并将结果存储到目标寄存器中。

备注

大于 15 位的移位操作结果不确定。

示例:

1:       LSH R1, R2, R3            // R1 = R2 << R3

2:       LSH R1, R2, 0x03          // R1 = R2 << 0x03

3:       .set value1, 0x03         // constant value1=0x03
         LSH R1, R2, value1        // R1 = R2 << value1

4:       .global label             // declaration of variable label
         LSH R1, R2, label         // R1 = R2 << label
         ...
  label:  nop                      // definition of variable label

RSH - 逻辑右移

语法

RSH Rdst, Rsrc1, Rsrc2

RSH Rdst, Rsrc1, imm

操作数

  • Rdst - 寄存器 R[0..3]

  • Rsrc1 - 寄存器 R[0..3]

  • Rsrc2 - 寄存器 R[0..3]

  • Imm - 16 位有符号值

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

该指令对源寄存器进行逻辑右移,移动的位数由另一个源寄存器或一个 16 位有符号值确定,并将结果存储到目标寄存器中。

备注

大于 15 位的移位操作结果未定义。

示例:

1:        RSH R1, R2, R3              // R1 = R2 >> R3

2:        RSH R1, R2, 0x03            // R1 = R2 >> 0x03

3:        .set value1, 0x03           // constant value1=0x03
          RSH R1, R2, value1          // R1 = R2 >> value1

4:        .global label               // declaration of variable label
          RSH R1, R2, label           // R1 = R2 >> label
  label:  nop                         // definition of variable label

MOVE – 移动到寄存器

语法

MOVE Rdst, Rsrc

MOVE Rdst, imm

操作数

  • Rdst – 寄存器 R[0..3]

  • Rsrc – 寄存器 R[0..3]

  • Imm – 16 位有符号值

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

该指令将源寄存器的值或一个 16 位有符号值移动到目标寄存器。

备注

注意,当标签用作立即数时,标签的地址会从字节转换为字。这是因为对于 LDSTJUMP 指令,地址寄存器的值应以字表示,而不以字节表示。更多详细信息请参阅 寻址注意事项

示例:

1:        MOVE       R1, R2            // R1 = R2

2:        MOVE       R1, 0x03          // R1 = 0x03

3:        .set       value1, 0x03      // constant value1=0x03
          MOVE       R1, value1        // R1 = value1

4:        .global     label            // declaration of label
          MOVE        R1, label        // R1 = address_of(label) / 4
          ...
  label:  nop                          // definition of label

ST – 将数据存储到内存中

语法

ST Rsrc, Rdst, offset

操作数

  • Rsrc – 寄存器 R[0..3],保存要存储的 16 位值

  • Rdst – 寄存器 R[0..3],目标地址,以 32 位字为单位

  • Offset – 13 位有符号值,以字节表示

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将 Rsrc 的 16 位值存储到目标内存地址 [Rdst + offset] 中,存储的数据占目标内存中一个字的低半部分,而高半部分由当前程序计数器 PC 的值(以字为单位,左移 5 位)与 Rdst(0..3)进行逻辑“或”运算:

Mem[Rdst + offset / 4]{31:0} = {PC[10:0], 3'b0, Rdst, Rsrc[15:0]}

应用程序可以使用高 16 位来确定 ULP 程序中的哪条指令将某个特定的字写入了内存。

备注

注意,以字节为单位指定的偏移量会在执行前被转换为 32 位字偏移量。更多信息请参阅 寻址注意事项

示例:

1:        ST  R1, R2, 0x12        // MEM[R2 + 0x12 / 4] = R1

2:        .data                   // Data section definition
  Addr1:  .word     123           // Define label Addr1 16 bit
          .set      offs, 0x00    // Define constant offs
          .text                   // Text section definition
          MOVE      R1, 1         // R1 = 1
          MOVE      R2, Addr1     // R2 = Addr1
          ST        R1, R2, offs  // MEM[R2 +  0 / 4] = R1
                                  // MEM[Addr1 + 0] will be 32'h600001

STL – 将数据存储到 32 位内存的低 16 位

语法

STL Rsrc, Rdst, offset, Label

操作数

  • Rsrc – 寄存器 R[0..3],保存要存储的 16 位值

  • Rdst – 寄存器 R[0..3],目标地址,以 32 位字为单位

  • Offset – 11 位有符号值,以字节为单位的偏移

  • Label – 用户定义的 2 位无符号值

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将 Rsrc 的 16 位值存储到地址为 [Rdst + offset / 4] 的内存的低半字中:

Mem[Rdst + offset / 4]{15:0} = {Rsrc[15:0]}
Mem[Rdst + offset / 4]{15:0} = {Label[1:0],Rsrc[13:0]}

STSTL 命令可以互换使用,以保持对早期版本 ULP 核的向后兼容性。

备注

注意,以字节为单位指定的偏移量会在执行前被转换为 32 位字偏移量。更多信息请参阅 寻址注意事项

示例:

1:        STL  R1, R2, 0x12           // MEM[R2 + 0x12 / 4] = R1

2:        .data                       // Data section definition
  Addr1:  .word     123               // Define label Addr1 16 bit
          .set      offs, 0x00        // Define constant offs
          .text                       // Text section definition
          MOVE      R1, 1             // R1 = 1
          MOVE      R2, Addr1         // R2 = Addr1
          STL       R1, R2, offs      // MEM[R2 +  0 / 4] = R1
                                      // MEM[Addr1 + 0] will be 32'hxxxx0001
3:
          MOVE      R1, 1             // R1 = 1
          STL       R1, R2, 0x12, 1   // MEM[R2 + 0x12 / 4] = 0xxxxx4001

STH – 将数据存储到 32 位内存的高 16 位

语法

STH Rsrc, Rdst, offset, Label

操作数

  • Rsrc – 寄存器 R[0..3],保存要存储的 16 位值

  • Rdst – 寄存器 R[0..3],目标地址,以 32 位字为单位

  • Offset – 11 位有符号值,以字节为单位的偏移

  • Label – 用户定义的 2 位无符号值

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将 Rsrc 的 16 位值存储到地址为 [Rdst + offset / 4] 的内存的高半字中:

Mem[Rdst + offset / 4]{31:16} = {Rsrc[15:0]}
Mem[Rdst + offset / 4]{31:16} = {Label[1:0],Rsrc[13:0]}

备注

注意,以字节为单位指定的偏移量会在执行前被转换为 32 位字偏移量。更多信息请参阅 寻址注意事项

示例:

1:        STH  R1, R2, 0x12           // MEM[R2 + 0x12 / 4][31:16] = R1

2:        .data                       // Data section definition
  Addr1:  .word     123               // Define label Addr1 16 bit
          .set      offs, 0x00        // Define constant offs
          .text                       // Text section definition
          MOVE      R1, 1             // R1 = 1
          MOVE      R2, Addr1         // R2 = Addr1
          STH       R1, R2, offs      // MEM[R2 +  0 / 4] = R1
                                      // MEM[Addr1 + 0] will be 32'h0001xxxx
3:
          MOVE      R1, 1             // R1 = 1
          STH       R1, R2, 0x12, 1   // MEM[R2 + 0x12 / 4] 0x4001xxxx

ST32 – 将 32 位数据存储到 32 位内存

语法

ST32 Rsrc, Rdst, offset, Label

操作数

  • Rsrc – 寄存器 R[0..3],保存要存储的 16 位值

  • Rdst – 寄存器 R[0..3],目标地址,以 32 位字为单位

  • Offset – 11 位有符号值,以字节为单位的偏移

  • Label – 用户定义的 2 位无符号值

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将 PC 的 11 位值,标签值和 Rsrc 的 16 位值存储到地址为 [Rdst + offset / 4] 的 32 位内存中:

Mem[Rdst + offset / 4]{31:0} = {PC[10:0],0[2:0],Label[1:0],Rsrc[15:0]}

备注

注意,以字节为单位指定的偏移量会在执行前被转换为 32 位字偏移量。更多信息请参阅 寻址注意事项

示例:

1:        ST32  R1, R2, 0x12, 0       // MEM[R2 + 0x12 / 4][31:0] = {PC[10:0],0[2:0],Label[1:0],Rsrc[15:0]}

2:        .data                       // Data section definition
  Addr1:  .word     123               // Define label Addr1 16 bit
          .set      offs, 0x00        // Define constant offs
          .text                       // Text section definition
          MOVE      R1, 1             // R1 = 1
          MOVE      R2, Addr1         // R2 = Addr1
          ST32      R1, R2, offs, 1   // MEM[R2 +  0] = {PC[10:0],0[2:0],Label[1:0],Rsrc[15:0]}
                                      // MEM[Addr1 + 0] will be 32'h00010001

STO – 设置自动递增操作的偏移值

语法

STO offset

操作数

  • Offset – 11 位有符号值,以字节为单位的偏移

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将 16 位值设置到偏移寄存器:

offset = value / 4

备注

注意,以字节为单位指定的偏移量会在执行前被转换为 32 位字偏移量。更多信息请参阅 寻址注意事项

示例:

1:        STO  0x12               // Offset = 0x12 / 4

2:        .data                   // Data section definition
  Addr1:  .word     123           // Define label Addr1 16 bit
          .set      offs, 0x00    // Define constant offs
          .text                   // Text section definition
          STO       offs          // Offset = 0x00

STI – 将数据存储到 32 位内存中,并自动递增预定义地址偏移

语法

STI Rsrc, Rdst, Label

操作数

  • Rsrc – 寄存器 R[0..3],保存要存储的 16 位值

  • Rdst – 寄存器 R[0..3],目标地址,以 32 位字为单位

  • Label – 用户定义的 2 位无符号值

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将 Rsrc 的 16 位值存储到地址为 [Rdst + offset / 4] 的内存的低半字和高半字中。

当 STI 指令被调用两次时,会自增偏移量。请确保在执行 STI 指令之前,执行 STO 指令来设置该偏移值:

Mem[Rdst + offset / 4]{15:0/31:16} = {Rsrc[15:0]}
Mem[Rdst + offset / 4]{15:0/31:16} = {Label[1:0],Rsrc[13:0]}

示例:

1:        STO  4                  // Set offset to 4
          STI  R1, R2             // MEM[R2 + 4 / 4][15:0] = R1
          STI  R1, R2             // MEM[R2 + 4 / 4][31:16] = R1
                                  // offset += (1 * 4) //offset is incremented by 1 word
          STI  R1, R2             // MEM[R2 + 8 / 4][15:0] = R1
          STI  R1, R2             // MEM[R2 + 8 / 4][31:16] = R1

STI32 – 将 32 位数据存储到 32 位内存中,并自动递增地址偏移

语法

STI32 Rsrc, Rdst, Label

操作数

  • Rsrc – 寄存器 R[0..3],保存要存储的 16 位值

  • Rdst – 寄存器 R[0..3],目标地址,以 32 位字为单位

  • Label – 用户定义的 2 位无符号值

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将 PC 的 11 位值,标签值和 Rsrc 的 16 位值存储到地址为 [Rdst + offset / 4] 的 32 位内存中。

每次调用 STI32 指令时,偏移值都会自动增加。确保在执行 STI32 指令之前,执行 STO 指令来设置偏移值:

Mem[Rdst + offset / 4]{31:0} = {PC[10:0],0[2:0],Label[1:0],Rsrc[15:0]}

示例:

1:        STO    0x12
          STI32  R1, R2, 0    // MEM[R2 + 0x12 / 4][31:0] = {PC[10:0],0[2:0],Label[1:0],Rsrc[15:0]}
                              // offset += (1 * 4) //offset is incremented by 1 word
          STI32  R1, R2, 0    // MEM[R2 + 0x16 / 4][31:0] = {PC[10:0],0[2:0],Label[1:0],Rsrc[15:0]}

LD – 从内存中加载数据

语法

LD Rdst, Rsrc, offset

操作数

  • Rdst – 寄存器 R[0..3],目标寄存器

  • Rsrc – 寄存器 R[0..3],保存目标地址,以 32 位字为单位

  • Offset – 13 位有符号值,以字节为单位的偏移量

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将地址为 [Rsrc + offset / 4] 的内存中的 16 位低半字加载到目标寄存器 Rdst 中:

Rdst[15:0] = Mem[Rsrc + offset / 4][15:0]

备注

注意,以字节为单位指定的偏移量会在执行前被转换为 32 位字偏移量。详情请参阅 寻址注意事项

示例:

1:        LD  R1, R2, 0x12            // R1 = MEM[R2 + 0x12 / 4]

2:        .data                       // Data section definition
  Addr1:  .word     123               // Define label Addr1 16 bit
          .set      offs, 0x00        // Define constant offs
          .text                       // Text section definition
          MOVE      R1, 1             // R1 = 1
          MOVE      R2, Addr1         // R2 = Addr1 / 4 (address of label is converted into words)
          LD        R1, R2, offs      // R1 = MEM[R2 +  0]
                                      // R1 will be 123

LDL – 从 32 位内存的低半字中加载数据

语法

LDL Rdst, Rsrc, offset

操作数

  • Rdst – 寄存器 R[0..3],目标寄存器

  • Rsrc – 寄存器 R[0..3],保存目标地址,以 32 位字为单位

  • Offset – 13 位有符号值,以字节为单位的偏移量

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将地址为 [Rsrc + offset / 4] 的内存中的 16 位低半字加载到目标寄存器 Rdst 中:

Rdst[15:0] = Mem[Rsrc + offset / 4][15:0]

LDLDL 命令可以互换使用,以保持对早期版本 ULP 核的向后兼容性。

备注

注意,以字节为单位指定的偏移量会在执行前被转换为 32 位字偏移量。更多信息请参阅 寻址注意事项

示例:

1:        LDL  R1, R2, 0x12           // R1 = MEM[R2 + 0x12 / 4]

2:        .data                       // Data section definition
  Addr1:  .word     123               // Define label Addr1 16 bit
          .set      offs, 0x00        // Define constant offs
          .text                       // Text section definition
          MOVE      R1, 1             // R1 = 1
          MOVE      R2, Addr1         // R2 = Addr1 / 4 (address of label is converted into words)
          LDL       R1, R2, offs      // R1 = MEM[R2 +  0]
                                      // R1 will be 123

LDH – 从 32 位内存的高半字加载数据

语法

LDH Rdst, Rsrc, offset

操作数

  • Rdst – 寄存器 R[0..3],目标寄存器

  • Rsrc – 寄存器 R[0..3],保存目标地址,以 32 位字为单位

  • Offset – 13 位有符号值,以字节为单位的偏移

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令将地址为 [Rsrc + offset / 4] 的内存中的 16 位高半字加载到目标寄存器 Rdst 中:

Rdst[15:0] = Mem[Rsrc + offset / 4][15:0]

备注

注意,以字节为单位指定的偏移量会在执行前被转换为 32 位字偏移量。更多信息请参阅 寻址注意事项

示例:

1:        LDH  R1, R2, 0x12           // R1 = MEM[R2 + 0x12 / 4]

2:        .data                       // Data section definition
  Addr1:  .word     0x12345678        // Define label Addr1 16 bit
          .set      offs, 0x00        // Define constant offs
          .text                       // Text section definition
          MOVE      R1, 1             // R1 = 1
          MOVE      R2, Addr1         // R2 = Addr1 / 4 (address of label is converted into words)
          LDH       R1, R2, offs      // R1 = MEM[R2 +  0]
                                      // R1 will be 0x1234

JUMP – 跳转到绝对地址

语法

JUMP Rdst

JUMP ImmAddr

JUMP Rdst, Condition

JUMP ImmAddr, Condition

操作数

  • Rdst – 寄存器 R[0..3] 包含要跳转到的地址(以 32 位字表示)

  • ImmAddr – 13 位地址(以字节表示),对齐为 4 字节

  • Condition: - EQ – 如果最后的 ALU 操作结果为零,则跳转 - OV – 如果最后的 ALU 设置了溢出 flag,则跳转

周期

执行需要 2 个周期,获取下一条指令需要 2 个周期。

描述

该指令跳转到指定的地址。既可以无条件跳转,也可以基于 ALU flag 跳转。

示例:

1:        JUMP       R1            // Jump to address in R1 (address in R1 is in 32-bit words)

2:        JUMP       0x120, EQ     // Jump to address 0x120 (in bytes) if ALU result is zero

3:        JUMP       label         // Jump to label
          ...
  label:  nop                      // Definition of label

4:        .global    label         // Declaration of global label

          MOVE       R1, label     // R1 = label (value loaded into R1 is in words)
          JUMP       R1            // Jump to label
          ...
  label:  nop                      // Definition of label

JUMPR – 跳转到相对偏移(条件基于 R0)

语法

JUMPR Step, Threshold, Condition

操作数

  • Step – 相对于当前位置的偏移量,以字节为单位

  • Threshold – 分支条件的阈值

  • Condition
    • EQ (等于) – 如果 R0 中的值 == 阈值,则跳转

    • LT (小于) – 如果 R0 中的值 < 阈值,则跳转

    • LE (小于或等于) – 如果 R0 中的值 <= 阈值,则跳转

    • GT (大于) – 如果 R0 中的值 > 阈值,则跳转

    • GE (大于或等于) – 如果 R0 中的值 >= 阈值,则跳转

周期

条件 EQGTLT:执行需要 2 个周期,获取下一条指令需要 2 个周期。

条件 LEGE 在汇编程序中使用两个 JUMPR 指令实现:

// JUMPR target, threshold, LE is implemented as:

         JUMPR target, threshold, EQ
         JUMPR target, threshold, LT

// JUMPR target, threshold, GE is implemented as:

         JUMPR target, threshold, EQ
         JUMPR target, threshold, GT

因此,执行时间取决于所用分支:要么执行 2 个周期 + 获取 2 个周期,要么执行 4 个周期 + 获取 4 个周期。

描述

如果条件为真,该指令会跳转到相对地址。条件是指 R0 寄存器的值和阈值的比较结果。

示例:

1:pos:    JUMPR       16, 20, GE   // Jump to address (position + 16 bytes) if value in R0 >= 20

2:        // Down counting loop using R0 register
          MOVE        R0, 16       // load 16 into R0
  label:  SUB         R0, R0, 1    // R0--
          NOP                      // do something
          JUMPR       label, 1, GE // jump to label if R0 >= 1

JUMPS – 跳转到相对地址(条件基于阶段数)

语法

JUMPS 步骤, 阈值, 条件

操作数

  • 步骤 – 相对于当前位置的偏移,以字节为单位

  • 阈值 – 分支条件的阈值

  • 条件
    • EQ (等于) – 如果 stage_cnt == 阈值,则跳转

    • LT (小于) – 如果 stage_cnt < 阈值,则跳转

    • LE (小于或等于) - 如果 stage_cnt <= 阈值,则跳转

    • GT (大于) – 如果 stage_cnt > 阈值,则跳转

    • GE (大于或等于) — 如果 stage_cnt >= 阈值,则跳转

周期

执行需要 2 个周期,获取下一条指令需要 2 个周期。

描述

如果条件为真,指令将跳转到相对地址。条件是计数寄存器的值和阈值的比较结果。

示例:

1:pos:    JUMPS     16, 20, EQ     // Jump to (position + 16 bytes) if stage_cnt == 20

2:        // Up counting loop using stage count register
          STAGE_RST                  // set stage_cnt to 0
  label:  STAGE_INC  1               // stage_cnt++
          NOP                        // do something
          JUMPS       label, 16, LT  // jump to label if stage_cnt < 16

STAGE_RST – 重置阶段计数寄存器

语法

STAGE_RST

操作数

无操作数

描述

该指令将阶段计数寄存器设置为 0

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

示例:

1:       STAGE_RST      // 重置阶段计数寄存器

STAGE_INC – 增加阶段计数寄存器

语法

STAGE_INC

操作数

  • – 8 位值

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期。

描述

该指令将给定值增加到阶段计数寄存

示例:

1:        STAGE_INC      10          // stage_cnt += 10

2:        // Up counting loop example:
          STAGE_RST                  // set stage_cnt to 0
  label:  STAGE_INC  1               // stage_cnt++
          NOP                        // do something
          JUMPS      label, 16, LT   // jump to label if stage_cnt < 16

STAGE_DEC – 减少阶段计数寄存器

语法

STAGE_DEC

操作数

  • – 8 位值

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

该指令从阶段计数寄存器中减去给定值

示例:

1:        STAGE_DEC      10        // stage_cnt -= 10;

2:        // Down counting loop example
          STAGE_RST                // set stage_cnt to 0
          STAGE_INC  16            // increment stage_cnt to 16
  label:  STAGE_DEC  1             // stage_cnt--;
          NOP                      // do something
          JUMPS      label, 0, GT  // jump to label if stage_cnt > 0

HALT – 结束程序

语法

HALT

操作数

无操作数

周期

执行需要 2 个周期

描述

该指令会停止 ULP 协处理器并重新启动 ULP 唤醒定时器(如果定时器已启用)

示例:

1:       HALT      // Halt the coprocessor

WAKE – 唤醒芯片

语法

WAKE

操作数

无操作数

周期

执行需要 2 个周期,获取下一条指令需要 4 个周期

描述

该指令从 ULP 协处理器向 RTC 控制器发送一个中断。

  • 如果 SoC 处于深度睡眠模式并启用了 ULP 唤醒,会唤醒 SoC。

  • 如果 SoC 不处在深度睡眠模式,并且在 RTC_CNTL_INT_ENA_REG 寄存器中设置了 ULP 中断位 (RTC_CNTL_ULP_CP_INT_ENA),则会触发 RTC 中断。

备注

注意,在使用 WAKE 指令前,ULP 程序可能需要等待 RTC 控制器就绪,才能唤醒主 CPU。此信息通过 RTC_CNTL_LOW_POWER_ST_REG 寄存器的 RTC_CNTL_RDY_FOR_WAKEUP 位来指示。当 RTC_CNTL_RDY_FOR_WAKEUP 为零时,执行 WAKE 指令唤醒无效。如果希望在主 CPU 不处于睡眠模式时使用 WAKE 指令,可以用 RTC_CNTL_LOW_POWER_ST_REG 的 RTC_CNTL_MAIN_STATE_IN_IDLE 位(位 27)来检查主 CPU 状态,确定其处于正常模式还是睡眠模式。

示例:

1: is_rdy_for_wakeup:                   // Read RTC_CNTL_RDY_FOR_WAKEUP bit
          READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
          AND r0, r0, 1
          JUMP is_rdy_for_wakeup, eq    // Retry until the bit is set
          WAKE                          // Trigger wake up
          REG_WR 0x006, 24, 24, 0       // Stop ULP timer (clear RTC_CNTL_ULP_CP_SLP_TIMER_EN)
          HALT                          // Stop the ULP program
          // After these instructions, SoC will wake up,
          // and ULP will not run again until started by the main program.

1: check_wakeup:                        // Read RTC_CNTL_RDY_FOR_WAKEUP and RTC_CNTL_MAIN_STATE_IN_IDLE bit
          READ_RTC_REG(RTC_CNTL_LOW_POWER_ST_REG, 27, 1)
          MOVE r1, r0                   // Copy result in to r1
          READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
          OR r0, r0, r1
          JUMP check_wakeup, eq         // Retry until either of the bit are set
          WAKE                          // Trigger wake up
          HALT                          // Stop the ULP program

WAIT – 等待一定的周期数

语法

WAIT Cycles

操作数

  • Cycles – 等待的周期数

周期

执行需要 (2 + Cycles) 个周期,获取下一条指令需要 4 个周期

描述

该指令指示延迟一定的周期数。

示例:

1:        WAIT     10         // Do nothing for 10 cycles

2:        .set  wait_cnt, 10  // Set a constant
          WAIT  wait_cnt      // wait for 10 cycles

TSENS – 使用温度传感器进行测量

语法

  • TSENS Rdst, Wait_Delay

操作数

  • Rdst – 目标寄存器 R[0..3],结果将存储到此寄存器

  • Wait_Delay – 执行测量所需的周期数

周期

执行需要(2 + Wait_Delay + 3 * TSENS_CLK)个周期,获取下一条指令需要 4 个周期

描述

该指令使用 TSENS 进行测量,并将结果存储到通用寄存器

示例:

1:        TSENS     R1, 1000     // Measure temperature sensor for 1000 cycles,
                                // and store result to R1

ADC – 使用 ADC 进行测量

语法

  • ADC Rdst, Sar_sel, Mux

  • ADC Rdst, Sar_sel, Mux, 0 — 形式已弃用

操作数

  • Rdst – 目标寄存器 R[0..3],结果将存储到此寄存器

  • Sar_sel – 选择 ADC:0 = SARADC1,1 = SARADC2

  • Mux - 选择的 PAD,SARADC Pad[Mux-1] 被启用。如果传递了 Mux 值 1,会使用 ADC pad 0。

周期

执行需要 23 + max(1, SAR_AMP_WAIT1) + max(1, SAR_AMP_WAIT2) + max(1, SAR_AMP_WAIT3) + SARx_SAMPLE_CYCLE + SARx_SAMPLE_BIT 个周期,获取下一条指令需要 4 个周期

描述

该指令通过 ADC 进行测量

示例

1:        ADC      R1, 0, 1      // Measure value using ADC1 pad 2 and store result into R1

REG_RD – 从外设寄存器读取

语法

REG_RD Addr, High, Low

操作数

  • Addr – 寄存器地址,以 32 位字为单位

  • High – 寄存器结束位号

  • Low – 寄存器起始位号

周期

执行需要 4 个周期,获取下一条指令需要 4 个周期

描述

该指令从外设寄存器读取最多 16 位到一个通用寄存器:R0 = REG[Addr][High:Low]

该指令可以访问 RTC_CNTL、RTC_IO、SENS 和 RTC_I2C 外设中的寄存器。从 ULP 看到的寄存器地址可以根据 PeriBUS1 总线上相同寄存器的地址计算得出,如下所示:

addr_ulp = (addr_peribus1 - DR_REG_RTCCNTL_BASE) / 4

示例:

1:        REG_RD      0x120, 7, 4     // load 4 bits: R0 = {12'b0, REG[0x120][7:4]}

REG_WR – 写入外设寄存器

语法

REG_WR Addr, High, Low, Data

操作数

  • Addr – 寄存器地址,以 32 位字为单位

  • High – 寄存器结束位号

  • Low – 寄存器起始位号

  • Data – 要写入的值,8 位

周期

执行需要 8 个周期,获取下一条指令需要 4 个周期

描述

该指令将一个立即数的最多 8 位写入到外设寄存器中:REG[Addr][High:Low] = data

此指令可以访问 RTC_CNTL 、 RTC_IO 、 SENS 和 RTC_I2C 外设中的寄存器。从 ULP 看到的寄存器地址可以根据 PeriBUS1 上同一寄存器的地址按如下方式计算:

addr_ulp = (addr_peribus1 - DR_REG_RTCCNTL_BASE) / 4

示例:

1:        REG_WR      0x120, 7, 0, 0x10   // set 8 bits: REG[0x120][7:0] = 0x10

方便的外设寄存器访问宏

ULP 源文件在进入汇编程序之前先通过 C 预处理器,因此可以使用某些宏来方便地访问外设寄存器。

一些现有的宏定义在 soc/soc_ulp.h 头文件中,这些宏允许通过的名称访问外设寄存器的字段。可以通过这些宏使用的外设寄存器名称定义在 soc/rtc_cntl_reg.hsoc/rtc_io_reg.hsoc/sens_reg.hsoc/rtc_i2c_reg.h 中。

READ_RTC_REG(rtc_reg, low_bit, bit_width)

将 rtc_reg[low_bit + bit_width - 1 : low_bit] 中的数读到 R0,最多 16 位。如:

#include "soc/soc_ulp.h"
#include "soc/rtc_cntl_reg.h"

/*  RTC_CNTL_TIME0_REG 的低 16 位读入 R0 */
READ_RTC_REG(RTC_CNTL_TIME0_REG, 0, 16)
READ_RTC_FIELD(rtc_reg, field)

将 rtc_reg 的一个字段读取到 R0,最多 16 位。如:

#include "soc/soc_ulp.h"
#include "soc/sens_reg.h"

/*  SENS_SAR_SLAVE_ADDR3_REG  8  SENS_TSENS_OUT 字段读入 R0 */
READ_RTC_FIELD(SENS_SAR_SLAVE_ADDR3_REG, SENS_TSENS_OUT)
WRITE_RTC_REG(rtc_reg, low_bit, bit_width, value)

将立即数写入 rtc_reg[low_bit + bit_width - 1 : low_bit],最多 8 位。如:

#include "soc/soc_ulp.h"
#include "soc/rtc_io_reg.h"

/* 设置 RTC_GPIO_OUT_W1TS_REG  RTC_GPIO_OUT_DATA_W1TS 字段的 BIT(2) */
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 2, 1, 1)
WRITE_RTC_FIELD(rtc_reg, field, value)

将立即数写入 rtc_reg 的一个字段,最多 8 位。如:

#include "soc/soc_ulp.h"
#include "soc/rtc_cntl_reg.h"

/*  RTC_CNTL_STATE0_REG  RTC_CNTL_ULP_CP_SLP_TIMER_EN 字段设置为 0 */
WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0)