# SmartDMA / EZH 技术概要 本文档整理 NXP AN14650《SmartDMA Cookbook》中与 `sdmacc` 编译器设计相关的信息。SmartDMA 在文档中也称为 EZH,二者指同一类 NXP MCU 内的轻量可编程协处理单元。 ## 资料来源 - NXP HTML 文档: - NXP PDF 文档: - 文档版本:AN14650 Rev. 1.0,2025-05-28。 ## 定位 SmartDMA / EZH 不是替代 Arm 主核的通用 CPU,而是用于卸载重复性、事件驱动、I/O 密集和实时性敏感任务的辅助处理单元。 典型收益: - 减少 Arm 中断响应路径带来的延迟。 - 对 GPIO、外部事件和布尔条件有更直接的硬件支持。 - 可在数据搬运过程中做格式转换或简单处理,因此比普通 DMA 更灵活。 - 支持确定性执行机制,例如心跳节拍、等待节拍、全指令节拍同步和硬件 tight loop。 - 通过指令 AHB 与数据 AHB 分离,降低取指和数据访问互相阻塞的概率。 典型用途: - RGB565 到 RGB888 等数据格式转换。 - 键盘扫描、GPIO 状态机、流式 GPIO 输出。 - 软件模拟 USART、I2C、SPI、ISO7816、PWM 等简单时序协议。 - CRC、SHA 等移位型算法中的辅助计算。 - 图像或大块数据预处理。 - 多路正交编码器检测。 ## 系统结构 EZH 在系统中作为 AHB controller 工作,使用双总线结构: - `Inst-AHB`:专门用于取 opcode。 - `Data-AHB`:用于 literal、只读数据、读写数据和片上 memory/peripheral 访问。 这种结构的一个重要语义是:PC 与取指流水线相关,EZH 的 `PC` 通常位于当前执行指令之后两条指令的位置。分支、literal 读取、PC 相对寻址和代码生成都必须考虑这一点。 EZH 还包含: - 轻量处理器核心。 - ALU。 - barrel shifter。 - bit-slice 布尔检测逻辑。 - GPIO 直接访问接口。 - 中断/事件输出。 - AHB 访问接口。 ## 外设寄存器 EZH 控制寄存器位于 APB 地址空间。不同芯片族的基地址不同: | 芯片族 | EZH 控制寄存器基地址 | | --- | --- | | LPC5410x | `0x4004C000` | | LPC54114 / LPC51U68 | `0x4001D000` | | LPC55(S)6x / LPC55(S)2x | `0x4001D000` | | LPC55(S)3x | `0x4001D000` | | i.MX RT500 | `0x40027000` | | MCXNx4x / MCXN23x | `0x40033000` | 主要控制寄存器: | 名称 | 偏移 | 作用 | | --- | ---: | --- | | `EZHB_BOOT` | `0x20` | 32-bit 启动地址,需 4 字节对齐。 | | `EZHB_CTRL` | `0x24` | 控制启动、外部 flag、AHB 同步、AHB error 策略等。高 16 位必须写 `0xC0DE`。 | | `EZHB_PC` | `0x28` | 只读程序计数器。 | | `EZHB_SP` | `0x2C` | 只读栈指针。 | | `EZHB_BREAK_ADDR` | `0x30` | breakpoint 匹配地址。 | | `EZHB_BREAK_VECT` | `0x34` | breakpoint 服务例程地址。 | | `EZHB_EMER_VECT` | `0x38` | emergency 例程地址。 | | `EZHB_EMER_SEL` | `0x3C` | software emergency 请求与使能。 | | `EZHB_ARM2EZH` | `0x40` | Arm 到 EZH 的通用通信/控制字段。 | | `EZHB_EZH2ARM` | `0x44` | EZH 到 Arm 的通用通信字段;在特定配置下写入可触发 Arm 中断。 | | `EZHB_PENDTRAP` | `0x48` | pending trap enable、polarity、request/status。 | `EZHB_CTRL` 重点位: - `[31:16]`:必须为 `0xC0DE`。 - `B4`:使能 AHB synchronization。 - `B3`:选择 AHB write bufferable / nonbufferable。 - `B2`:是否忽略 AHB bus error;为 0 时 bus error 触发 emergency。 - `B1`:external flag,影响 `EX` / `NEX` 条件。 - `B0`:start bit,置位后启动 EZH 执行。 ## 内部寄存器 所有 EZH 内部寄存器为 32-bit。 | 寄存器 | 读写 | 作用 | | --- | --- | --- | | `R0` - `R7` | RW | 通用寄存器。 | | `GPO` | RW | EZH GPIO 输出寄存器,bit 0-31 对应 GPIO 输出状态。 | | `GPD` | RW | EZH GPIO 方向寄存器,1 为输出,0 为输入。 | | `GPI` | RO | EZH GPIO 输入寄存器。 | | `CFS` | RW | bit-slice source 配置;低 8 位反映当前 bit-slice 输入状态。 | | `CFM` | RW | bit-slice event 配置;低 8 位控制输出路由。 | | `SP` | RW | 栈指针;即使不主动使用栈也需要初始化。 | | `PC` | RW | 程序计数器;通常领先当前执行指令两条。 | | `RA` | RW | 返回地址寄存器。 | 如果 `CFM`、`GPD`、`GPI` 等专用功能未使用,文档说明它们也可作为通用寄存器使用。编译器是否利用这些寄存器,需要结合 ABI 和目标程序特性谨慎定义。 ## 执行模型 ### ALU EZH ALU 支持 32-bit 加减和逻辑运算,常见操作包括: - `ADD` - `SUB` - `ADC` - `SBC` - `AND` - `OR` - `XOR` - `ANDOR` EZH 的一个关键特性是可在单周期内组合执行 `FEND/FBIT + LSR + ALU + S` 这类操作。也就是说,预移位、字节序翻转、位翻转、ALU 运算和置 flag 可以组合进同一类指令形式。代码生成阶段应尽量利用这些融合形式。 ### Barrel Shifter barrel shifter 支持: - `LSL` - `LSR` - `ASR` - `ROR` 立即数 shift 范围为 0-31。寄存器 shift 使用寄存器低 8 位作为 shift/rotate 数量。对于 32 或更大移位量,结果和 carry 行为有专门规则,后端生成移位代码时不能直接套用 C 语言的未定义行为。 ### Flip / Invert EZH 支持在 ALU 前做: - `FEND`:字节序翻转。 - `FBIT`:bit 顺序翻转。 - invert:按位取反。 这对 CRC、哈希、字节序转换和像素格式转换很重要。 ### Flags EZH 条件执行依赖 flag。flag 可来自 ALU/MOV/LOAD immediate、bit-slice 事件或 external flag。 ALU flag: | Flag | 近似 Arm 条件 | 作用 | | --- | --- | --- | | `EU` | 无 | 无条件执行。 | | `ZE` | `EQ` | Zero。 | | `PO` | `PL` / `GE` | Positive。 | | `NE` | `MI` | Negative。 | | `AZ` | `GT` | Above zero。 | | `ZB` | `LE` | Zero or below。 | | `CA` | `CS` | Carry set。 | | `NC` | `CC` | Carry not set。 | | `CZ` | 无 | Carry set and zero。 | 算法/分支控制 flag: - `SPO` / `UNS` - `SNE` / `NZS` 它们可用于移位型算法和 scheduled branch。`SPO`、`SNE` 的更新规则不同于普通 `PO`、`NE`,适合实现“每轮都移位,但有条件执行 ALU”的算法。 外部/布尔检测 flag: - `EX`:external flag set。 - `NEX`:external flag not set。 - `BS`:bit-slice logical combiner set。 - `NBS`:bit-slice logical combiner not set。 ## GPIO EZH 支持 32-bit GPIO 直接访问: - `GPO` 控制输出值。 - `GPD` 控制方向。 - `GPI` 读取输入值。 与通过 AHB 访问普通 GPIO 外设相比,直接寄存器访问延迟更低。`E_MODIFY_GPO_BYTE` 可在单周期内对 `GPO` 低 8 位执行 clear/set/toggle 组合操作,适合严格时序的 GPIO 波形生成。 ## Bit-slice 布尔检测 EZH 有 8 个 bit-slice 布尔检测引擎。每个 bit-slice 可从 `BS_INPUT0` 到 `BS_INPUT7` 中选择一个输入,输入来源通常通过芯片 INPUTMUX 绑定到 GPIO、中断、timer match 等触发源。 `CFS` 配置输入源: - `CFS[31:8]`:每个 bit-slice 使用 3 bit 选择输入。 - `CFS[7:0]`:反映当前 bit-slice 输入状态。 `CFM` 配置事件检测: | 3-bit 配置 | 行为 | | --- | --- | | `000` | 恒为 1。 | | `001` | sticky rising edge。 | | `010` | sticky falling edge。 | | `011` | sticky any edge。 | | `100` | high level。 | | `101` | low level。 | | `110` | 恒为 0。 | | `111` | any edge。 | sticky edge 需要软件写 `CFM` 清除后才能检测下一次事件。 每个 bit-slice 输出可以: - 路由到 logical combiner。 - 级联到下一个 bit-slice。 `CFM[7:0]` 控制每个 bit-slice 的 OR enable。logical combiner 输出可驱动 `BS` / `NBS` 条件、`E_HOLD`、`E_VECTORED_HOLD` 和相关事件等待机制。 ## HOLD 与事件向量 `E_HOLD` 会暂停执行,直到布尔条件匹配。 `E_VECTORED_HOLD` 可以等待多个 bit-slice 条件,并在某个条件触发后跳转到对应 vector table entry。它类似“事件等待 + 跳转/调用”。默认 vector table 有 8 个 entry,对应 8 个 bit-slice。 注意点: - `E_VECTORED_HOLD` 后面的两条指令会被流水线 flush,不执行,因此有额外事件处理延迟。 - `E_ACC_VECTORED_HOLD` 可加速指定事件,保留后续指令在流水线中,使特定事件触发后更快执行。 - large vector table 形式每个 entry 包含 4 条指令,便于 scheduled branch 加上若干立即执行指令。 - 写 `CFM` 或 `CFS` 会清除 `BS` flag。 ## Pending Trap `EZHB_PENDTRAP` 提供 pending trap 机制,可锁存输入 IO 边沿/电平变化,并作为 bit-slice 的触发源。 字段含义: - `[23:16]`:pending trap enable。 - `[15:8]`:pending trap status polarity。 - `[7:0]`:pending trap request/status。 用途: - 锁存输入变化,直到软件清除。 - 通过 polarity 配置检测上升或下降方向。 - 由 Arm 软件修改低 8 位,间接触发 bit-slice/vector 行为。 ## Heartbeat Timer EZH 有 16-bit heartbeat down counter。`E_HEART_RYTHM` 设置 reload 值并启动计数。默认 reload 为 0,此时每个周期都有 beat;文档说明该模式下 counter 会 clock-gate 以节省功耗。 相关指令: - `E_HEART_RYTHM`:设置 heartbeat。 - `E_WAIT_FOR_BEAT`:等待下一个 beat。 - `E_SYNCH_ALL_TO_BEAT`:切换为所有指令按 beat 同步执行。 用途: - 串行采样等固定节拍任务。 - 降低执行速率以省电。 - 在共享 memory/AHB 场景下获得更确定的执行节奏。 ## Tight Loop `E_TIGHT_LOOP` 是硬件循环机制,避免传统 decrement/compare/branch 循环的额外分支开销。 语义要点: - 循环执行次数为 `1 + loop_counter`。 - `E_TIGHT_LOOP` 后的第一条指令只在初次进入时执行一次。 - 设置 tight loop 使用的寄存器在循环体中可复用,硬件已捕获循环参数。 编译器后端可以将固定次数小循环或简单 counted loop 映射到 `E_TIGHT_LOOP`,但必须保证循环体边界和首条指令一次性执行语义正确。 ## 栈与返回地址 EZH 有 `SP` 和 `RA`,但栈管理主要由软件负责: - `SP` 需要初始化。 - `E_PUSH` / `E_POP` 是基于 `SP` 的 load/store 包装。 - 当前文档描述的栈为 upward-growing stack。 - `E_GOSUB` 和默认的 `E_VECTORED_HOLD` 会将返回地址备份到 `RA`。 - 嵌套调用时,用户或编译器必须显式保存/恢复 `RA`。 - `E_GOTO` 可通过 `L` 选项保存返回地址到 `RA`。 对 `sdmacc` 来说,需要尽早定义最小 ABI: - 哪些寄存器用于表达式临时值。 - 是否支持函数调用。 - `RA` 由 caller 还是 callee 保存。 - 是否启用栈,以及栈空间由 Arm 侧启动代码如何传入。 ## Breakpoint 与 Emergency Breakpoint: - `EZHB_BREAK_ADDR` 指定断点地址。 - `EZHB_BREAK_VECT` 指定断点服务例程。 - 执行到匹配地址时,EZH 用跳转到 vector 的方式替换该指令。 - 不建议把断点放在 scheduled branch 附近,否则程序流难以还原。 Emergency: - `EZHB_EMER_VECT` 指定 emergency 例程。 - external emergency 或 AHB error 可触发 emergency。 - 进入 emergency 后不能通过普通代码退出,只能 reset EZH 或拉低 ignition/start。 ## Arm / EZH 通信与中断 EZH 可通过三种机制向 Arm 触发中断: - `E_INT_TRIGGER(n)`:触发 common interrupt output 和具体 interrupt output channel。 - `EZHB_ARM2EZH` / `EZHB_EZH2ARM` 握手:当 `EZHB_ARM2EZH[1:0] == 0b10`,写 `EZHB_EZH2ARM` 可触发 Arm 中断。 - interrupt hijack:EZH 输出最多 32-bit interrupt bus,用于模拟不同外设中断源。 Arm 侧也需要: - 打开 EZH clock/reset。 - 配置 PINMUX 和 INPUTMUX。 - 配置 NVIC 中 EZH 的 IRQ。 - 设置 `EZHB_BOOT`、`EZHB_ARM2EZH`、`EZHB_CTRL` 后启动 EZH。 ## 指令集概要 AN14650 将 EZH 指令集概括为: | 指令族 | 功能 | | --- | --- | | `E_MOV`, `E_LOAD_SIMM`, `E_LOAD_IMM` | 数据移动和立即数加载。 | | `E_ADD` | 加法,可带 postshift。 | | `E_SUB` | 减法,可带 postshift。 | | `E_ADC` | 带 carry 加法。 | | `E_SBC` | 带 carry 减法。 | | `E_AND` | 位与。 | | `E_OR` | 位或。 | | `E_XOR` | 位异或。 | | `E_ANDOR` | AND 后 OR 的组合逻辑。 | | `E_LSL`, `E_LSR`, `E_ASR`, `E_ROR` | 预移位/旋转和 ALU 组合。 | | `E_FEND`, `E_FBIT` | 字节序/bit 顺序翻转后参与右移和 ALU。 | | `E_RLSL`, `E_RLSR`, `E_RASR`, `E_RROR` | 寄存器控制的移位/旋转。 | | `E_BTST`, `E_BSET`, `E_BCLR`, `E_BTOG` | bit test/set/clear/toggle。 | | `E_MODIFY_GPO_BYTE` | 单周期修改 `GPO` 低字节。 | | `E_TIGHT_LOOP` | 零额外分支开销循环。 | | `E_HOLD` | 等待布尔模式匹配。 | | `E_NOP` | 空操作。 | | `E_HEART_RYTHM` | 设置 heartbeat counter。 | | `E_SYNCH_ALL_TO_BEAT` | 所有指令同步到 beat。 | | `E_WAIT_FOR_BEAT` | 等待下一次 beat。 | | `E_INT_TRIGGER` | 触发 interrupt output。 | | `E_GOTO` | 分支;scheduled branch 单周期,普通分支约三周期。 | | `E_GOSUB` | 子程序调用,写 `RA`。 | | `E_LDR` | 从片上地址读取数据到 EZH register。 | | `E_STR` | 从 EZH register 写数据到片上地址。 | | `E_LDR_REG` | register address 形式读取。 | | `E_STR_REG` | register address 形式写入。 | | `E_PER_READ`, `E_PER_WRITE` | 访问 Cortex-M peripheral region。 | | `E_PUSH`, `E_POP` | 栈 push/pop。 | 指令命名按流水线执行顺序组合。例如 `E_SUBN_LSLS` 表示 subtract、取反、logical shift left、set flags。 常见后缀/符号: | 符号 | 含义 | | --- | --- | | `COND` | 条件执行。 | | `N` | ALU 结果取反。 | | `S` | 设置 flags;在 `LDRB` 场景也可表示 signed access。 | | `F` / `FEND` | 字节序翻转。 | | `FBIT` | bit 顺序翻转。 | | `LSL` / `LSR` / `ASR` / `ROR` | 移位/旋转。 | | `IMM` | 立即数。 | | `R` / `REG` | register 操作数。 | | `NRA` | 不更新 `RA`。 | | `LV` | large vector table。 | | `ACC` | accelerated vectored hold。 | | `PER` | peripheral access。 | | `BTST` / `BSET` / `BCLR` / `BTOG` | bit 操作。 | ## 对 sdmacc 的编译器设计影响 ### 初始目标建议 第一阶段不要直接实现完整 C。建议先定义 SmartDMA 友好的 C 子集: - `uint32_t` / `int32_t` 为主。 - 简单局部变量。 - 常量表达式和寄存器内表达式。 - `if` / `while` / 固定次数 `for`。 - 指针读写,用于 memory/peripheral 访问。 - 内建函数或扩展语法映射 GPIO、interrupt、hold、heartbeat、tight loop 等 EZH 特性。 ### 后端优先级 后端应优先覆盖: 1. 寄存器分配:先支持 `R0`-`R7`,再决定是否把未使用的专用寄存器纳入通用池。 2. 立即数加载:`E_LOAD_IMM` 仅支持有限范围立即数,32-bit 常量需要多指令合成或 literal load。 3. 条件执行:将简单 `if` 映射为条件指令,减少分支。 4. 循环:识别 counted loop 并使用 `E_TIGHT_LOOP`。 5. 内存访问:区分普通 memory load/store 与 peripheral access。 6. 函数调用:若支持函数,必须明确 `RA` 和 `SP` 保存策略。 7. 事件等待:用 intrinsic 暴露 `E_HOLD`、`E_VECTORED_HOLD`、heartbeat 等非 C 原生语义。 ### 需要继续确认的信息 AN14650 对架构、寄存器、指令族和伪指令形式有较完整描述,但要做 standalone compiler 仍需进一步确认: - 每条指令的最终 32-bit binary encoding。 - NXP SDK 头文件中 EZH pseudo instruction macro 的编码细节。 - 不同芯片族是否存在 ISA 子集差异或寄存器行为差异。 - SmartDMA 代码段放置、启动参数、栈地址和 Arm 侧 runtime glue 的约定。 - 是否需要直接生成机器码,还是先生成 NXP macro assembly / C header,再由 Arm 工具链集成。 对本项目而言,建议下一步从 NXP SDK 的 `fsl_smartdma_prv.h` 或等价头文件提取 opcode encoding,形成 `docs/smartdma-isa.md`,再开始实现 lexer/parser 之前的目标 ISA 数据模型。