121 lines
5.2 KiB
ReStructuredText
121 lines
5.2 KiB
ReStructuredText
ULP coprocessor programming
|
|
===========================
|
|
|
|
.. warning:: ULP coprocessor programming approach described here is experimental. It is probable that once binutils support for ULP is done, this preprocessor-based approach may be deprecated. We welcome discussion about and contributions to ULP programming tools.
|
|
|
|
ULP coprocessor is a simple FSM which is designed to perform measurements using ADC, temperature sensor, and external I2C sensors, while main processors are in deep sleep mode. ULP coprocessor can access RTC_SLOW_MEM memory region, and registers in RTC_CNTL, RTC_IO, and SARADC peripherals. ULP coprocessor uses fixed-width 32-bit instructions, 32-bit memory addressing, and has 4 general purpose 16-bit registers.
|
|
|
|
ULP coprocessor doesn't have a dedicated binutils port yet. Programming ULP coprocessor is possible by embedding assembly-like macros into an ESP32 application.
|
|
Here is an example how this can be done::
|
|
|
|
const ulp_insn_t program[] = {
|
|
I_MOVI(R3, 16), // R3 <- 16
|
|
I_LD(R0, R3, 0), // R0 <- RTC_SLOW_MEM[R3 + 0]
|
|
I_LD(R1, R3, 1), // R1 <- RTC_SLOW_MEM[R3 + 1]
|
|
I_ADDR(R2, R0, R1), // R2 <- R0 + R1
|
|
I_ST(R2, R3, 2), // R2 -> RTC_SLOW_MEM[R2 + 2]
|
|
I_HALT()
|
|
};
|
|
size_t load_addr = 0;
|
|
size_t size = sizeof(program)/sizeof(ulp_insn_t);
|
|
ulp_process_macros_and_load(load_addr, program, &size);
|
|
ulp_run(load_addr);
|
|
|
|
The ``program`` array is an array of ``ulp_insn_t``, i.e. ULP coprocessor instructions. Each ``I_XXX`` preprocessor define translates into a single 32-bit instruction. Arguments of these preprocessor defines can be register numbers (``R0 — R3``) and literal constants. See `ULP coprocessor instruction defines`_ section for descriptions of instructions and arguments they take.
|
|
|
|
Load and store instructions use addresses expressed in 32-bit words. Address 0 corresponds to the first word of ``RTC_SLOW_MEM`` (which is address 0x50000000 as seen by the main CPUs).
|
|
|
|
To generate branch instructions, special ``M_`` preprocessor defines are used. ``M_LABEL`` define can be used to define a branch target. Label identifier is a 16-bit integer. ``M_Bxxx`` defines can be used to generate branch instructions with target set to a particular label.
|
|
|
|
Implementation note: these ``M_`` preprocessor defines will be translated into two ``ulp_insn_t`` values: one is a token value which contains label number, and the other is the actual instruction. ``ulp_process_macros_and_load`` function resolves the label number to the address, modifies the branch instruction to use the correct address, and removes the the extra ``ulp_insn_t`` token which contains the label numer.
|
|
|
|
Here is an example of using labels and branches::
|
|
|
|
const ulp_insn_t program[] = {
|
|
I_MOVI(R0, 34), // R0 <- 34
|
|
M_LABEL(1), // label_1
|
|
I_MOVI(R1, 32), // R1 <- 32
|
|
I_LD(R1, R1, 0), // R1 <- RTC_SLOW_MEM[R1]
|
|
I_MOVI(R2, 33), // R2 <- 33
|
|
I_LD(R2, R2, 0), // R2 <- RTC_SLOW_MEM[R2]
|
|
I_SUBR(R3, R1, R2), // R3 <- R1 - R2
|
|
I_ST(R3, R0, 0), // R3 -> RTC_SLOW_MEM[R0 + 0]
|
|
I_ADDI(R0, R0, 1), // R0++
|
|
M_BL(1, 64), // if (R0 < 64) goto label_1
|
|
I_HALT(),
|
|
};
|
|
RTC_SLOW_MEM[32] = 42;
|
|
RTC_SLOW_MEM[33] = 18;
|
|
size_t load_addr = 0;
|
|
size_t size = sizeof(program)/sizeof(ulp_insn_t);
|
|
ulp_process_macros_and_load(load_addr, program, &size);
|
|
ulp_run(load_addr);
|
|
|
|
|
|
Functions
|
|
^^^^^^^^^
|
|
|
|
.. doxygenfunction:: ulp_process_macros_and_load
|
|
.. doxygenfunction:: ulp_run
|
|
|
|
Error codes
|
|
^^^^^^^^^^^
|
|
|
|
.. doxygendefine:: ESP_ERR_ULP_BASE
|
|
.. doxygendefine:: ESP_ERR_ULP_SIZE_TOO_BIG
|
|
.. doxygendefine:: ESP_ERR_ULP_INVALID_LOAD_ADDR
|
|
.. doxygendefine:: ESP_ERR_ULP_DUPLICATE_LABEL
|
|
.. doxygendefine:: ESP_ERR_ULP_UNDEFINED_LABEL
|
|
.. doxygendefine:: ESP_ERR_ULP_BRANCH_OUT_OF_RANGE
|
|
|
|
ULP coprocessor registers
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
ULP co-processor has 4 16-bit general purpose registers. All registers have same functionality, with one exception. R0 register is used by some of the compare-and-branch instructions as a source register.
|
|
|
|
These definitions can be used for all instructions which require a register.
|
|
|
|
.. doxygengroup:: ulp_registers
|
|
:content-only:
|
|
|
|
ULP coprocessor instruction defines
|
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
|
|
.. doxygendefine:: I_DELAY
|
|
.. doxygendefine:: I_HALT
|
|
.. doxygendefine:: I_ST
|
|
.. doxygendefine:: I_LD
|
|
.. doxygendefine:: I_BL
|
|
.. doxygendefine:: I_BGE
|
|
.. doxygendefine:: I_BXR
|
|
.. doxygendefine:: I_BXI
|
|
.. doxygendefine:: I_BXZR
|
|
.. doxygendefine:: I_BXZI
|
|
.. doxygendefine:: I_BXFR
|
|
.. doxygendefine:: I_BXFI
|
|
.. doxygendefine:: I_ADDR
|
|
.. doxygendefine:: I_SUBR
|
|
.. doxygendefine:: I_ANDR
|
|
.. doxygendefine:: I_ORR
|
|
.. doxygendefine:: I_MOVR
|
|
.. doxygendefine:: I_LSHR
|
|
.. doxygendefine:: I_RSHR
|
|
.. doxygendefine:: I_ADDI
|
|
.. doxygendefine:: I_SUBI
|
|
.. doxygendefine:: I_ANDI
|
|
.. doxygendefine:: I_ORI
|
|
.. doxygendefine:: I_MOVI
|
|
.. doxygendefine:: I_LSHI
|
|
.. doxygendefine:: I_RSHI
|
|
.. doxygendefine:: M_LABEL
|
|
.. doxygendefine:: M_BL
|
|
.. doxygendefine:: M_BGE
|
|
.. doxygendefine:: M_BX
|
|
.. doxygendefine:: M_BXZ
|
|
.. doxygendefine:: M_BXF
|
|
|
|
Defines
|
|
^^^^^^^
|
|
|
|
.. doxygendefine:: RTC_SLOW_MEM
|
|
|