PIO Underlying Principles Deep‑Dive: Side‑Set to Instruction Execution
RP2040 PIO includes side‑set, wrap, FIFO join, auto‑push/pull, fractional divider and flexible GPIO mapping.
0
Project description
【Preliminary Note】The original hardware example in this article was written based on the RP2040. The actual hardware used in this hands-on demonstration features the W55RP20 as the main controller chip. The circuit logic and UF2 flashing operation principles are universally applicable, with only the main controller model differing. The original chip model mentioned in the circuit descriptions below is provided for reference purposes only.

Detailed Explanation of PIO Functions and Features
1. Side-set Characteristics

| Advantage points | Specific Instructions | Typical Scenarios |
| timing accuracy | The main instruction and GPIO control are completed within the same clock cycle without instruction latency, which eliminates the timing deviation caused by separate SET instructions. | Clock and data synchronization for SPI/I2C, and timing control for high-speed serial communication |
| Code Refactoring | No additional SET/OUT instructions are required to control GPIO, which reduces the number of program instructions and lowers the storage space occupied by finite-state machine programs | High-frequency data transmission scenarios (e. g. SPI_TX_FAST) |
| Frequency Boost | Reduce the instruction execution cycle and increase the maximum operating frequency supported by peripherals (for example, SPI can achieve a higher clock frequency) | High-speed data transmission (e. g. high-speed SPI mode) |
| Mapping Flexibility | The GPIO mapping of Side-set is independent of the mappings of SET and OUT; it can be either overlapping or separate, and supports complex peripheral control logic. | Independent control of I2C SDA/SCL and multi-signal parallel output |
Delay/side-set field of the instruction to reuseDelay/side-set field to reuse:PINCTRL_SIDESET_COUNT: if set to 1, it occupies 1 bit, leaving 4 bits available for delay; if set to 5, all 5 bits are used for sideset and no delay function is available.PINCTRL_SIDESET_BASE) that specifies the starting GPIO number for sideset control, which is independent of PINCTRL_SET_BASE for SET and PINCTRL_OUT_BASE for OUT;side <value> identifier that follows the instruction.
| 配置项 | 对应的寄存器字段 | 功能定义 | 取值 / 说明 |
| 1. 侧集数据位数 | PINCTRL_SIDESET_COUNT | 定义Delay/side-set字段中用于侧集的位数(剩余位用于延时) | 0~5: - 0:禁用 Side-set; - 1~5:侧集占用的位数(如 1 表示 1 位侧集,最多控制 1 个 GPIO;5 表示 5 位侧集,无法使用延时) |
| 2. 有效位使能 | EXECCTRL_SIDE_EN | 决定 Side-set 是否需要有效位才能生效 | - 假(默认):只要SIDESET_COUNT>0,所有指令的 Side-set 都生效; - 真:仅当指令设置了有效位时,Side-set 才生效(用于选择性启用侧集) |
| 3. 起始 GPIO 编号 | PINCTRL_SIDESET_BASE | 定义 Side-set 控制的最低 GPIO 编号(侧集的第 0 位对应该 GPIO,第 n 位对应SIDESET_BASE+n) | 0~GPIO 最大编号(如设置为 6,表示侧集第 0 位控制 GPIO6,第 1 位控制 GPIO7) |
| 4. 控制类型(电平 / 方向) | EXECCTRL_SIDE_PINDIR | 决定 Side-set 修改的是 GPIO 的电平,还是 GPIO 的方向(输入 / 输出) |
a line-by-line breakdown of the spi_tx_fast program, combined with configuration instructions for the practical use of Side-set:.program spi_tx_fast ; 定义了一个名为 spi_tx_fast 的程序
.side_set 1 ; 设置侧集数据位数为1
loop:
out pins, 1 side 0 ;将1个数据位传输到GPIO引脚1上,同时使用Side-set操作设置将其设置为低电平
jmp loop side 1 ; 这行指令跳转回 loop 标签,同时 Side-set 操作设置将其设置为高电平. side_set 1, the 4 configuration items of this example are set as follows:
| 配置项 | 设置值 | 原因 / 作用 |
| PINCTRL_SIDESET_COUNT | 1 | 程序指定侧集位数为 1,对应控制 1 个 GPIO(SPI 的时钟 CLK) |
| EXECCTRL_SIDE_EN | 假 | 所有指令都需要执行 Side-set,无需有效位筛选 |
| PINCTRL_SIDESET_BASE | 时钟 GPIO 编号(如 6) | 侧集控制的 GPIO 为 SPI 的 CLK 引脚(GPIO6) |
| EXECCTRL_SIDE_PINDIR | 假 | 控制的是 CLK 引脚的电平(高 / 低),而非方向 |
spi_tx_fast Example:out pins, 1 side 0 When executed, data output is completed synchronously with the CLK going low. jmp loop side 1 When executed, the jump is completed synchronously with the CLK going high. The entire loop takes only 2 clock cycles to output 1 bit of SPI data, enabling high-frequency data transmission.spi_tx_fast This example demonstrates two advantages of side-set: more precise alignment between data and clock, faster overall program execution — in this case, one data bit can be output every two system clock cycles — and a smaller program size.The Delay/side-set field is to reuse. The more bits occupied by the side-set, the fewer bits available for delay (for example, if the side-set occupies 5 bits, the delay function cannot be used; if it occupies 1 bit, the maximum delay is 15 (4 bits)).When EXECCTRL_SIDE_EN is set to true, only the Side-set of some instructions takes effect, which is applicable to scenarios requiring selective GPIO control (e. g., toggling the clock only during data transmission and performing no operation when idle).When EXECCTRL_SIDE_PINDIR is set to true, the Side-set modifies the GPIO direction, which is applicable to scenarios requiring dynamic switching of GPIO input/output (such as bidirectional communication on the SDA pin of I2C).2. Program Reentry
EXECCTRL_WRAP_BOTTOM and EXECCTRL_WRAP_TOP) to make the program counter (PC) automatically jump to the preset start address (WRAP_BOTTOM) when it reaches the specified address (WRAP_TOP), instead of executing a software-level JMP jump instruction.This feature specifically addresses the issues of instruction memory occupancy and execution cycle overhead caused by traditional JMP loops, and serves as a core hardware optimization method to improve the execution efficiency of PIO programs and save instruction memory.
| 问题点 | 具体说明 | 影响 |
| 指令内存浪费 | JMP 指令需要占用 1 个独立的指令内存位置,而该位置本可用于实现其他程序逻辑 | 减少了 PIO 有限的指令内存(最多 32 条指令)的利用率 |
| 执行周期损耗 | JMP 指令本身需要消耗 1 个时钟周期,且会打破指令执行的连续性 | 降低程序的最大执行速度(如方波生成的频率会因 JMP 的周期损耗减半) |
.program squarewave
set pindirs, 1 ; 步骤1:设置引脚为输出模式(1个周期)
again:
set pins, 1 [1] ; 步骤2:引脚置高,延时1周期(共2个周期:指令1个+延时1个)
set pins, 0 ; 步骤3:引脚置低(1个周期)
jmp again ; 步骤4:跳转回again(1个周期)jmp jump instruction itself takes one cycle, and each set instruction also takes one cycle, so to ensure that the high and low level signals are maintained for the same duration, the set pins, 1 instruction adds a one-cycle delay, making the finite-state machine wait for one cycle before executing the set pins, 0 instruction. Each loop takes a total of 4 cycles.JMP occupies instruction memory that could otherwise be allocated to other programsexecute JMP reduce the maximum output speed by half.PC) will automatically wrap around to 0 when it exceeds 31, filling the entire instruction memory with set pins, 1 and set pins, 0 can solve the second problem, but this approach wastes a significant amount of memory.EXECCTRL control register), thus solving both problems:
| 汇编指示符 | 对应硬件寄存器 | 功能定义 |
| .wrap_target | EXECCTRL_WRAP_BOTTOM | 标记程序折返的起始地址(PC 跳转的目标位置) |
| .wrap | EXECCTRL_WRAP_TOP | 标记程序折返的结束地址(PC 触发折返的位置) |
.program squarewave_wrap ; 折返版方波程序
set pindirs, 1 ; 初始化:设置引脚为输出模式(1个周期)
.wrap_target ; 标记折返起始地址(WRAP_BOTTOM = 1)
set pins, 1 [1] ; 引脚置高,延时1周期(共2个周期)
set pins, 0 [1] ; 引脚置低,延时1周期(共2个周期)
.wrap ; 标记折返结束地址(WRAP_TOP = 2)set pins, 1 [1](PC= 1), and increment PC by 1 to PC= 2 upon completion;set pins, 0 [1](PC= 2), upon completion, PC will be equal to WRAP_TOP (2), and automatically jump to WRAP_BOTTOM (1) instead of incrementing PC by 1;PC:JMP, and Condition is true, then set PC to TargetPC is equal to EXECCTRL_WRAP_TOP, set PC to EXECCTRL_WRAP_BOTTOMPC increments by 1; however, if the current value is 31, it will be set to 0pioasm . wrap_target and. wrap assembler directives are effectively labels; they export constants that are written to WRAP_BOTTOM and WRAP_TOP respectively.pioasm and compiled, will generate the following files:// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
# 如果没有定义 PICO_NO_HARDWARE,则会包含 hardware/pio.h 头文件,它定义了 PIO 相关的数据结构和函数
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --------------- //
// squarewave_wrap //
// --------------- //
# 两个宏定义了程序中 .wrap_target 和 .wrap 指令的位置
#define squarewave_wrap_wrap_target 1
#define squarewave_wrap_wrap 2
# 数组包含了 squarewave_wrap 程序的指令序列
static const uint16_t squarewave_wrap_program_instructions[] = {
0xe081, // 0: set pindirs, 1
// .wrap_target
0xe101, // 1: set pins, 1 [1]
0xe100, // 2: set pins, 0 [1]
// .wrap
};
# 如果没有定义 PICO_NO_HARDWARE
#if !PICO_NO_HARDWARE
# 定义一个 squarewave_wrap_program 结构体,其中包含了程序的指令序列和长度等信息
static const struct pio_program squarewave_wrap_program = {
.instructions = squarewave_wrap_program_instructions,
.length = 3,
.origin = -1,
};
# 返回一个默认的状态机配置,其中设置了 .wrap_target 和 .wrap 的位置
static inline pio_sm_config squarewave_wrap_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + squarewave_wrap_wrap_target, offset +
squarewave_wrap_wrap);
return c;
}
#endif. wrap_target and. wrap into constants and generate the corresponding configuration functions, specifically including:xxx_wrap_target and xxx_wrap macros to store the return relative address;the pio_program structure, which contains information such as the instruction sequence and its length.xxx_program_get_default_config function, which automatically writes the wrap address to pio_sm_config (by calling the sm_config_set_wrap function).WRAP_BOTTOM and WRAP_TOP are absolute addresses of PIO instruction memory, not relative addresses;pio_sm_set_wrap function or modify the EXECCTRL register's WRAP_BOTTOM / WRAP_TOP fields to configure the wrap address.WRAP_BOTTOM must be less than or equal to WRAP_TOP, and neither of them shall exceed the maximum address (31) of the PIO instruction memory; otherwise, the wrap-around logic will fail.WRAP_BOTTOM = 0 and WRAP_TOP = 31), the PC will automatically reset to 0 when it reaches 31. This is the natural wrap-around feature of the PIO instruction memory, which can be regarded as the default case of program wrap-around.[n]) or side set (side n) features; both the delay and side set will still take effect normally.3. FIFO Merge

| FIFO 类型 | 数据流向 | 深度 | 状态机操作指令 |
| TX FIFO | 系统 → 状态机 | 4 字 | PULL(读取) |
| RX FIFO | 状态机 → 系统 | 4 字 | PUSH(写入) |
| 这种结构适合双向数据传输的场景,但如果程序只需要单向传输(如仅发送数据的 UART-TX、仅接收数据的 UART-RX),另一个 FIFO 会处于空闲状态,造成资源浪费。 |
SHIFTCTRL_FJOIN option to combine two 4-word-long FIFOs into one 8-word-long FIFO.PULL read (when merged as a TX FIFO), or for the system to read (when merged as an RX FIFO);FSTAT register (the second figure you provided). When merged, the "unused FIFO" will be in both the full (FULL) and empty (EMPTY) states simultaneously:FSTAT. TXEMPTY / TXFULL indicates the actual status;FSTAT. RXEMPTY and RXFULL will be set to 1 simultaneously (indicating that the RX FIFO is both full and empty and cannot be used);FSTAT. RXEMPTY / RXFULL indicates the actual status;FSTAT. TXEMPTY and TXFULL will be set to 1 simultaneously.the PUSH instruction (writing data to the RX FIFO) and keeps waiting (since the depth of the RX FIFO is 0, no data can be written);the PULL instruction (to read data from the TX FIFO) and will keep waiting (since the depth of the TX FIFO is 0, there is no data available to read).4. Auto-ejection and Auto-loading
.program manual_pull
.side_set 1 opt ; 设置 Side-set 侧集操作位数为1,opt 以指定 side <value> 对于指令是可选的
.wrap_target ; 标记了 .wrap 指令的目标位置
set x, 2 ; 将寄存器 x 初始化为 2,这个寄存器会用于控制循环次数
pull side 1 [1] ; 执行手动拉取操作,如果没有 TX 数据,则会在这里等待
bitloop: ; 这里定义了一个标签,用于循环输出数据位
out pins, 1 side 0 [1] ; 将 1 位数据移位输出到 GPIO 引脚,同时将 Side-set 设置为 0,用于控制时钟
jmp x-- bitloop side 1 [1] ; 递减寄存器 x,如果不为 0,则跳转回 bitloop 标签
out pins, 1 side 0 ; 输出最后一位数据,Side-set 设置为 0
.wrap ; 无条件跳转回 .wrap_target 标记的位置PULL;PUSH.OUT instruction, and the OSR shift counter accumulates the number of bits shifted out;SHIFTCTRL_PULL_THRESH (the configured threshold), hardware automatically triggers PULL;.program autopull
.side_set 1 ; 侧集位数1
.wrap_target
out pins, 1 side 0 [1] ; 移出1位数据,侧集时钟置0
nop side 1 [1] ; 空操作,侧集时钟置1(替代手动跳转)
.wrap IN instruction, and the ISR shift counter accumulates the number of bits shifted in;SHIFTCTRL_PUSH_THRESH (the configured threshold), hardware automatically triggers PUSH;manual_pull the example can be rewritten as follows:.program autopull
.side_set 1
.wrap_target
out pins, 1 side 0 [1]
nop side 1 [1]
.wrapSHIFTCTRL_PULL_THRESH) is reached, so the example program can also shift out 16 or 32 data bits from each FIFO word.out pins, 1 side 0 [1]instruction, data output occurs on the falling edge of the clock signal, while the nop side 1 [1]instruction is executed on the rising edge of the clock signal.Therefore, when the TX FIFO is empty, the finite-state machine will pause during the low level phase of the clock signal and resume execution only after new data enters the TX FIFO.PULL IFEMPTY instruction, which adopts the same configurable threshold as autopull; after using this instruction, it can simplify certain programs to control where the pause occurs just like autopull does. When both OSR and TX FIFO are empty, the finite-state machine will enter the pause state upon executing OUT..program somewhat_manual_pull
.side_set 1
.wrap_target
out pins, 1 side 0 [1]
pull ifempty side 1 [1]
.wrap.program auto_push_pull ; 定义了一个名为 auto_push_pull 的程序
.wrap_target
out x, 32 ; 将 32 位数据从寄存器 x 自动推送(auto-push)到 TX FIFO
in x, 32 ; 从 RX FIFO 自动拉取(auto-pull) 32 位数据到寄存器 x
.wrap#include "tb.h" // TODO this is built against existing sw tree, so that we get printf etc
#include "platform.h"
#include "pio_regs.h"
#include "system.h"
#include "hardware.h"
#include "auto_push_pull.pio.h"
int main()
{
// 用于初始化测试环境
tb_init();
// 加载并配置 PIO 状态机 0 来运行 auto_push_pull 程序
// 将 auto_push_pull_program 数组中的指令加载到 PIO 指令存储器中
for (int i = 0; i < count_of(auto_push_pull_program); ++i)
mm_pio->instr_mem[i] = auto_push_pull_program[i];
// 配置状态机 0 的 SHIFTCTRL 寄存器
// 启用自动推送和自动拉取功能,设置推送和拉取阈值为 32
mm_pio->sm[0].shiftctrl =
(1u << PIO_SM0_SHIFTCTRL_AUTOPUSH_LSB) |
(1u << PIO_SM0_SHIFTCTRL_AUTOPULL_LSB) |
(0u << PIO_SM0_SHIFTCTRL_PUSH_THRESH_LSB) |
(0u << PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB);
// 配置状态机 0 的 EXECCTRL 寄存器,设置 .wrap_target 和 .wrap 指令的位置
mm_pio->sm[0].execctrl =
(auto_push_pull_wrap_target << PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB) |
(auto_push_pull_wrap << PIO_SM0_EXECCTRL_WRAP_TOP_LSB);
// 启动状态机 0
hw_set_bits(&mm_pio->ctrl, 1u << (PIO_CTRL_SM_ENABLE_LSB + 0));
// 向 TX FIFO 中写入 5 个数据,并从 RX FIFO 中读取并打印出 5 个数据
for (int i = 0; i < 5; ++i)
mm_pio->txf[0] = i;
for (int i = 0; i < 5; ++i)
printf("%d\n", mm_pio->rxf[0]);
return 0;
}CTRL_SM_RESTART is triggered, the ISR shift counter is set to 0 (no data has been shifted in yet), and the OSR shift counter is set to 32 (no data is pending to be shifted out);OUT instruction increments the OSR shift counter by Bit count;IN instruction increments the ISR shift counter by Bit count;PULL instruction or automatic loading will clear the OSR counter to 0;The PUSH instruction or an automatic pop operation will clear the ISR counter to 0;MOV OSR, x or MOV ISR, x will clear the OSR or ISR shift counter to 0;OUT ISR, n instruction sets the ISR shift counter to n.OUT or IN instruction, the finite-state machine compares the shift counter against SHIFTCTRL_PULL_THRESH and SHIFTCTRL_PUSH_THRESH to determine whether an action should be taken. Auto-pull and auto-push are enabled by the SHIFTCTRL_AUTOPULL and SHIFTCTRL_AUTOPUSH fields respectively.4.1 Detailed Explanation of Autopush
// 读取数据并将其使用in指令位移到isr移位寄存器中
isr = shift_in(isr, input())
// 更新isr移位计数器的值,读取了几个数据递增几个数
// 当 isr count 大于32时,记为0
isr count = saturate(isr count + in count)
// 检查接收数据的位数统计 rx count 是否达到了预设的阈值
if rx count >= threshold:
// 如果接收 FIFO 已满,则暂停程序执行(stall)
if rx fifo is full:
stall
// 如果接收 FIFO 未满
else:
// 将 isr 中的数据推出到 RX FIFO
push(isr)
// 重置 isr 和 isr count 为 0,准备接收下一个数据包
isr = 0
isr count = 04.2 Detailed Explanation of Auto-Loading: autopull
// 若是执行了 MOV 或 PULL 指令
if MOV or PULL:
// 将输出移位寄存器 (OSR) 的位数统计 osr count 重置为 0
osr count = 0
// 检查 OSR 的位数统计 osr count 是否达到了预设的阈值
if osr count >= threshold:
// 如果发送 FIFO 不为空
if tx fifo not empty:
// 发送 FIFO 中拉取 (PULL) 数据到 OSR 中
osr = pull()
// 将 osr count 重置为 0,准备下一次发送
osr count = 0// 检查输出移位寄存器 (OSR) 的位数统计 osr count 是否达到了预设的阈值
if osr count >= threshold:
// 如果发送 FIFO 不为空
if tx fifo not empty:
// 从发送 FIFO 中拉取 (PULL) 数据到 OSR 中
osr = pull()
// 将 osr count 重置为 0,准备下一次发送
osr count = 0
// 无论TX FIFO是否为空,只要到达预设的阈值,暂停程序执行
stall
// 如果 osr count 未达到阈值
else:
// 将 OSR 中的数据输出
output(osr)
// 将 OSR 向左移位 out count 位,并将新数据填充到低位
osr = shift(osr, out count)
// 更新 osr count , 当 osr count 大于32时,记为0
osr count = saturate(osr count + out count)
// 再次检查 osr count 是否达到阈值
if osr count >= threshold:
// 如果发送 FIFO 不为空
if tx fifo not empty:
// 从发送 FIFO 中拉取数据到 OSR,并将 osr count 重置为 0
osr = pull()
osr count = 05. PIO-specific clock divider
Delay cycles is limited. For devices such as UART that require precise control and adjustment of signal rates, it is ideal that multiple finite-state machines running the same program can also adjust their rates independently. For this reason, each finite-state machine is equipped with a clock divider.n + f/256 , where n and f are the integer and fractional parts of the CLKDIV register of the fine-state machine. It selectively extends a certain frequency division period between n cycles and n + 1 cycles.INT) + fractional part (FRAC/256)(FRAC is 8-bit, ranging from 0 to 255, corresponding to a fraction between 0 and 1);6. GPIO Mapping
OUT instruction can write up to 32 data bits. Depending on the Destination field of the instruction, the instruction can be applied to the pin or pin direction. The lowest bit of the OUT data maps to the PINCTRL_OUT_BASE , sequentially maps PINCTRL_OUT_COUNT data bits backward, and returns after reaching GPIO31.SET instruction can write up to 5 data bits. Depending on the Destination field of the instruction, the instruction can be applied to the pin or discipline direction. The lowest bit of the SET data maps to the PINCTRL_SET_BASE , sequentially maps PINCTRL_SET_COUNT data bits backward, and returns after reaching GPIO31.side-set operation can write up to 5 data bits. Depending on the value of the EXECCTRL_SIDE_PINDIR register field, this operation can be applied to pins or pin directions. The least significant bit of the side-set data is mapped to PINCTRL_SIDESET_BASE, and the subsequent PINCTRL_SIDESET_COUNT data bits are mapped sequentially backward, wrapping around after reaching GPIO31.OUT / SET /side-set operation writes to a consecutive pin range, yet each range has its own independent size and location within the 32-data-bit GPIO space. This is already sufficiently flexible for most applications. For instance, if one finite-state machine implements an interface such as SPI on a set of pins, another finite-state machine can run the same program, mapped to a different set of pins, to provide a second SPI interface.OUT or SET, and can simultaneously execute one side-set. The pin mapping logic generates a write mask of 32 data bits, and writes the output level and output enable registers to the data bus according to the request content and pin mapping configuration.OUT / SET operation, side-set takes precedence in the overlapping area.6.1 Output Priority
OUT / SET / side-set the GPIO pins corresponding to the instructions, with the core rules as follows:OUT / SET / side-set each configure their mapping range via dedicated registers:OUT: Determined by PINCTRL_OUT_BASE (starting GPIO number) and PINCTRL_OUT_COUNT (number of pins), the least significant bit of data corresponds to OUT_BASE, and subsequent GPIOs are mapped in sequence (wrapping around after GPIO31);SET: determined by PINCTRL_SET_BASE and PINCTRL_SET_COUNT, following the same rules as OUT;side-set: determined by PINCTRL_SIDESET_BASE and PINCTRL_SIDESET_COUNT, following the same rules as OUT;side-set and OUT/SET within the same clock cycle, and the GPIOs mapped by the two overlap, the side-set operation takes effect preferentially (overwriting the corresponding pin values of OUT/SET);6.2 Output Priority
OUT / SET and side-set. In this way, each finite-state machine generates 32-bit write data and write masks for the GPIO output level and output enable registers.SET and side-set), or one finite-state machine changes the GPIO direction while another finite-state machine changes the level of the same GPIO. If no finite-state machine writes to the level or direction of a GPIO, the value remains unchanged.6.3 Input Mapping
IN receives instruction data in such a manner that the LSB is mapped to the GPIO set by PINCTRL_IN_BASE, subsequent higher bits come from GPIOs with sequentially higher numbers, and the sequence wraps around when reaching bit 31.the IN bus is the result of right-shifting the GPIO input value by PINCTRL_IN_BASE bits. If there are fewer than 32 GPIOs, the PIO input will be padded with 0s at the corresponding positions to make up the full 32 data bits.WAIT GPIO use absolute GPIO numbers instead of the indices in the IN data bus, and no right shift operation will be performed in this case.6.4 Input Synchronizer
IN PINS at any time, and only clean high and low levels will be observed, without any intermediate values that may interfere with the finite-state machine circuitry. This is particularly critical for asynchronous interfaces such as UART RX.INPUT_SYNC_BYPASS to bypass the synchronizer.7. Instruction Execution
MOV EXEC can execute instructions from a register specified by Source:MOV EXEC, <source>: source (e. g., X/Y/ISR/OSR) as the instruction to execute.OUT EXEC can execute the data migrated out via OSR:OUT EXEC, <bit_count> Executes the 16-bit data shifted out from OSR as an instruction;SMx_INSTR control register; the system can directly write instructions to this register for immediate execution:INSTR register, and only the lower 16 bits of the INSTR register are accessible)..program exec_example
hang:
jmp hang ; 定义了一个标签 hang,并在这里形成一个无限循环跳转
execute:
out exec, 32
jmp execute ; 定义了另一个标签 execute,在这里无限循环地将寄存器中的 32 位数据输出并执行
.program instructions_to_push
out x, 32 ; 将 32 位数据从寄存器 x 输出
in x, 32 ; 将 32 位数据从输入端口载入到寄存器 x
push ; 将寄存器 x 中的数据推送到 TX FIFO#include "tb.h" // TODO this is built against existing sw tree, so that we get printf etc
#include "platform.h"
#include "pio_regs.h"
#include "system.h"
#include "hardware.h"
#include "exec_example.pio.h"
int main()
{
tb_init();
// 将 exec_example_program 数组中的指令加载到 PIO 指令存储器中
for (int i = 0; i < count_of(exec_example_program); ++i)
mm_pio->instr_mem[i] = exec_example_program[i];
// 配置状态机 0 的 SHIFTCTRL 寄存器,启用自动拉取功能,并设置拉取计数器阈值为 32
mm_pio->sm[0].shiftctrl = (1u << PIO_SM0_SHIFTCTRL_AUTOPULL_LSB);
// 启动状态机 0,使其进入 hang 死循环
hw_set_bits(&mm_pio->ctrl, 1u << (PIO_CTRL_SM_ENABLE_LSB + 0));
// 强制状态机跳转到 execute 标签的位置,开始执行 out exec, 32 指令
mm_pio->sm[0].instr = 0x0000 | 0x1; // jmp execute
// 写入 instructions_to_push_program 中的指令到TX FIFO
// 写入一些数据(12345678)到TX FIFO
mm_pio->txf[0] = instructions_to_push_program[0]; // out x, 32
mm_pio->txf[0] = 12345678; // data to be OUTed
mm_pio->txf[0] = instructions_to_push_program[1]; // in x, 32
mm_pio->txf[0] = instructions_to_push_program[2]; // push
// 等待 RX FIFO 中有数据可读,并将其打印出来
while (mm_pio->fstat & (1u << PIO_FSTAT_RXEMPTY_LSB))
;
printf("%d\n", mm_pio->rxf[0]);
return 0;
}hang loop. While the finite-state machine is executing, the C program enforces a jmp instruction to make the finite-state machine jump out of the loop.INSTR register, the finite-state machine will immediately decode and execute this instruction instead of executing the instruction read from the PIO instruction memory. The program counter will not increment, so in the next cycle (assuming the instruction forced by the INSTR register does not cause a wait state), the finite-state machine will continue executing the current program from the original position, unless the written instruction modifies PC.INSTR register: the delay cycles in the instruction will be ignored and the instruction will be executed immediately, regardless of the setting of the clock divider of the finite-state machine. This interface is used to perform initialization or process flow control change, so the instruction will be executed as soon as possible regardless of the configuration of the finite-state machine.INSTR may trigger a wait state, during which the finite-state machine locks the instruction until it is completed. When this occurs, the EXECCTRL_EXEC_STALLED flag will be set. Resetting the finite-state machine or writing INSTR with NOP can clear this flag.OUT EXEC instruction. OUT itself requires one execution cycle, and the instruction executed by OUT will be carried out in the next execution cycle. Note that one of the instructions being executed is also OUT — the finite-state machine can only execute one OUT instruction per cycle.OUT EXEC writes the data shifted out by OUT into the internal instruction register. In the next cycle, the finite-state machine knows that it should not execute the instruction memory, but instead execute the content of this latch, and also knows that it should not increment PC at this point.INSTR causes a wait state, this instruction will be written to the same instruction register used by OUT EXEC and MOV EXEC, overwriting the instruction stored therein. Therefore, if the EXEC instruction is used, the instruction written to INSTR must not cause a wait state.