├── .github └── workflows │ ├── publish.yml │ └── run_tests.yaml ├── .gitignore ├── AUTHORS ├── CHANGES ├── LICENSE ├── MANIFEST.in ├── README.rst ├── demo.S ├── demo.sh ├── docs ├── disassembler.rst ├── index.rst └── preprocess.rst ├── esp32_ulp ├── __init__.py ├── __main__.py ├── assemble.py ├── definesdb.py ├── link.py ├── nocomment.py ├── opcodes.py ├── opcodes_s2.py ├── parse_to_db.py ├── preprocess.py ├── soc.py ├── soc_s2.py ├── soc_s3.py └── util.py ├── examples ├── blink.py ├── blink_s2.py ├── counter.py ├── counter_s2.py ├── readgpio.py ├── readgpio_s2.py └── readgpio_s3.py ├── package.json ├── sdist_upip.py ├── setup.py ├── tests ├── 00_unit_tests.sh ├── 01_compat_tests.sh ├── 02_compat_rtc_tests.sh ├── 03_disassembler_tests.sh ├── assemble.py ├── compat │ ├── alu.S │ ├── expr.S │ ├── fixes.S │ ├── io.S │ ├── jumps.S │ ├── loadstore.esp32s2.S │ ├── memory.S │ ├── preprocess_simple.S │ ├── reg.esp32.S │ ├── reg.esp32s2.S │ ├── sections.S │ ├── sleep.S │ └── symbols.S ├── decode.py ├── decode_s2.py ├── definesdb.py ├── esp32.ulp.ld ├── esp32_ulp ├── fixtures │ ├── all_opcodes-v.esp32.lst │ ├── all_opcodes-v.esp32s2.lst │ ├── all_opcodes.esp32.S │ ├── all_opcodes.esp32.lst │ ├── all_opcodes.esp32s2.S │ ├── all_opcodes.esp32s2.lst │ ├── incl.h │ ├── incl2.h │ ├── manual_bytes-v.esp32.lst │ ├── manual_bytes-v.esp32s2.lst │ ├── manual_bytes.esp32.lst │ └── manual_bytes.esp32s2.lst ├── link.py ├── nocomment.py ├── opcodes.py ├── opcodes_s2.py ├── preprocess.py ├── tools └── util.py └── tools ├── decode.py ├── decode_s2.py ├── disassemble.py ├── esp32_ulp └── genpkgjson.py /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | name: Publish Python Package 9 | 10 | on: 11 | # trigger when publishing a release 12 | release: 13 | types: [published] 14 | 15 | # also allow triggering this workflow manually for testing 16 | workflow_dispatch: 17 | 18 | jobs: 19 | publish: 20 | 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v3 26 | with: 27 | # just fetching 1 commit is not enough for setuptools-scm, so we fetch all 28 | fetch-depth: 0 29 | - name: Set up Python 30 | uses: actions/setup-python@v2 31 | with: 32 | python-version: '3.x' 33 | - name: Install dependencies 34 | run: | 35 | pip install setuptools setuptools_scm 36 | - name: Build package 37 | run: | 38 | python setup.py sdist 39 | rm dist/*.orig # clean sdist_upip noise 40 | - name: Publish to Test PyPI 41 | uses: pypa/gh-action-pypi-publish@release/v1 42 | with: 43 | password: ${{ secrets.TEST_PYPI_API_TOKEN }} 44 | repository_url: https://test.pypi.org/legacy/ 45 | - name: Publish to PyPI 46 | uses: pypa/gh-action-pypi-publish@release/v1 47 | if: github.event.release.tag_name # only when releasing a new version 48 | with: 49 | password: ${{ secrets.PYPI_API_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/run_tests.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | name: CI 9 | 10 | on: [push, pull_request] 11 | 12 | jobs: 13 | tests: 14 | runs-on: ubuntu-20.04 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | fetch-depth: 1 19 | 20 | - name: Install dependencies 21 | run: | 22 | sudo apt-get update 23 | sudo apt-get install -y git build-essential libffi-dev pkg-config python3 bison flex xxd 24 | 25 | - name: Record version 26 | run: | 27 | export VER=$(git describe --always --tags) 28 | echo ${VER} 29 | 30 | ###### Install tools ###### 31 | 32 | - name: Build MicroPython 33 | id: build_micropython 34 | run: | 35 | echo "Building micropython" 36 | git clone --depth 1 https://github.com/micropython/micropython.git 37 | pushd micropython/mpy-cross 38 | make 39 | popd 40 | pushd micropython/ports/unix 41 | git describe --always --tags 42 | make submodules 43 | make 44 | export OUTDIR=$PWD/build-standard 45 | export PATH=$PATH:$OUTDIR 46 | echo "bin_dir=$OUTDIR" >> $GITHUB_OUTPUT 47 | test $(micropython -c 'print("test")') = "test" 48 | popd 49 | 50 | - name: Fetch binutils-esp32ulp 51 | id: fetch_binutils 52 | run: | 53 | echo "Fetching URL of pre-built esp32ulp-elf binaries" 54 | ## URL to pre-built binaries is published in esp-idf 55 | IDFVER=v5.0.1 56 | curl -s \ 57 | -o tools.json \ 58 | https://raw.githubusercontent.com/espressif/esp-idf/$IDFVER/tools/tools.json 59 | URL=$(> $GITHUB_OUTPUT 75 | esp32ulp-elf-as --version | grep 'esp32ulp-elf' > /dev/null 76 | 77 | ###### Run tests ###### 78 | 79 | - name: Run unit tests 80 | id: unit_tests 81 | run: | 82 | export PATH=$PATH:${{ steps.build_micropython.outputs.bin_dir }} 83 | cd tests 84 | ./00_unit_tests.sh 85 | 86 | - name: Run compat tests 87 | id: compat_tests 88 | run: | 89 | export PATH=$PATH:${{ steps.build_micropython.outputs.bin_dir }} 90 | export PATH=$PATH:${{ steps.fetch_binutils.outputs.bin_dir }} 91 | cd tests 92 | ./01_compat_tests.sh 93 | 94 | - name: Run compat tests with RTC macros 95 | id: compat_rtc_tests 96 | run: | 97 | export PATH=$PATH:${{ steps.build_micropython.outputs.bin_dir }} 98 | export PATH=$PATH:${{ steps.fetch_binutils.outputs.bin_dir }} 99 | cd tests 100 | ./02_compat_rtc_tests.sh 101 | 102 | - name: Run disassembler tests 103 | id: disassembler_tests 104 | run: | 105 | export PATH=$PATH:${{ steps.build_micropython.outputs.bin_dir }} 106 | cd tests 107 | ./03_disassembler_tests.sh 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/binutils-gdb 2 | tests/esp-idf 3 | tests/ulptool 4 | tests/**/*.bin 5 | tests/**/*.elf 6 | tests/**/*.o 7 | tests/**/*.ulp 8 | tests/**/*.log 9 | tests/**/*.pre 10 | tests/log 11 | tests/*.lst 12 | tests/*.log 13 | tests/defines*.db 14 | demo.ulp 15 | *.pyc 16 | *.pyo 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | E-mail addresses listed here are not intended for support. 2 | 3 | micropython-esp32-ulp authors 4 | ----------------------------- 5 | micropython-esp32-ulp is written and maintained by various contributors: 6 | 7 | - Thomas Waldmann 8 | - Wilko Nienhaus 9 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | ChangeLog 2 | ========= 3 | 4 | 1.2.0, release 2022-03-26 5 | 6 | - project moved to micropython organization 7 | - project renamed from py-esp32-ulp to micropython-esp32-ulp 8 | - (no functional changes) 9 | 10 | 11 | 1.1.0, release 2021-12-03 12 | 13 | - first pypi release 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright 2018-2023 by the micropython-esp32-ulp authors, see AUTHORS file 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # stuff we need to include into the sdist is handled automatically by 2 | # setuptools_scm - it includes all git-committed files. 3 | # but we want only the main source code and exclude everything else 4 | # to not waste space on the esp32 5 | # (upip packages are not installable by pip on a PC, so on a PC one 6 | # would git clone anyway and get all the other files) 7 | exclude * # exclude all files in repo root 8 | prune .github 9 | prune docs 10 | prune examples 11 | prune tests 12 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. start-badges 2 | 3 | .. image:: ../../actions/workflows/run_tests.yaml/badge.svg 4 | :height: 20px 5 | :target: ../../actions/workflows/run_tests.yaml 6 | :alt: Build Status 7 | 8 | .. end-badges 9 | 10 | ===================== 11 | micropython-esp32-ulp 12 | ===================== 13 | 14 | micropython-esp32-ulp is an assembler toolchain for the ESP32 ULP (Ultra Low-Power) 15 | Co-Processor, written in MicroPython. 16 | 17 | It can translate small assembly language programs to a loadable/executable 18 | ULP-FSM (not RISC-V) machine code binary, directly on a ESP32 microcontroller. 19 | 20 | This is intended as an alternative approach to assembling such programs using 21 | the `binutils-gdb toolchain `_ 22 | (esp32-elf-as) from Espressif on a development machine. 23 | 24 | It can also be useful in cases where esp32-elf-as is not available. 25 | 26 | 27 | Features 28 | -------- 29 | 30 | The following features are supported: 31 | 32 | * the entire `ESP32 ULP instruction set `_ 33 | * the entire `ESP32-S2 ULP instruction set `_ 34 | (this also covers the ESP32-S3) [#f1]_ [#f2]_ 35 | * constants defined with ``.set`` 36 | * constants defined with ``#define`` 37 | * expressions in assembly code and constant definitions 38 | * RTC convenience macros (e.g. ``WRITE_RTC_REG``) 39 | * many ESP32 ULP code examples found on the web will work unmodified 40 | * a simple disassembler is also provided 41 | 42 | .. [#f1] Note: the ESP32-S2 and ESP32-S3 have the same ULP binary format between each other 43 | but the binary format is different than that of the original ESP32 ULP. You need to 44 | select the ``esp32s2`` cpu (`see docs `_) when assembling code for 45 | use on an ESP32-S2/S3. 46 | 47 | .. [#f2] Note: The ESP32-S2 and ESP32-S3 have the same ULP binary format, but the peripheral 48 | register addresses (those accessed with REG_RD and REG_WR) are different. For best 49 | results, use the correct peripheral register addresses for the specific variant you 50 | are working with. The assembler (when used with ``cpu=esp32s2``) will accept 51 | addresses for any of the 3 variants, because they are translated into relative 52 | offsets anyway and many registers live at the same relative offset on all 3 variants. 53 | This conveniently means that the same assembly code can assembled unmodified for each 54 | variant and produce a correctly working binary - as long as only peripheral registers 55 | are used, which have the same relative offset across the variants. Use with care! 56 | 57 | 58 | Quick start 59 | ----------- 60 | 61 | To get going run the following directly on the ESP32: 62 | 63 | .. code-block:: python 64 | 65 | # IMPORTANT: Ensure the ESP32 is connected to a network with internet connectivity. 66 | 67 | # Step 1: Install micropython-esp32-ulp (for MicroPython v1.20 or newer) 68 | import mip 69 | mip.install('github:micropython/micropython-esp32-ulp') 70 | 71 | # Step 1: Install micropython-esp32-ulp (for MicroPython older than v1.20) 72 | import upip 73 | upip.install('micropython-esp32-ulp') 74 | 75 | # Step 2: Run an example 76 | # First, upload examples/counter.py to the ESP32. 77 | import counter 78 | 79 | The `examples/counter.py `_ example shows how to assemble code, 80 | load and run the resulting binary and exchange data between the ULP and the main CPU. 81 | 82 | 83 | Documentation 84 | ------------- 85 | See `docs/index.rst `_. 86 | 87 | 88 | Requirements 89 | ------------ 90 | 91 | The minimum supported version of MicroPython is v1.12. (For ESP32-S2 and S3 92 | devices, a version greater than v1.20 is required as versions before that 93 | did not enable the ``esp32.ULP`` class). 94 | 95 | An ESP32 device is required to run the ULP machine code binary produced by 96 | micropython-esp32-ulp. 97 | 98 | 99 | License 100 | ------- 101 | 102 | This project is released under the `MIT License `_. 103 | 104 | -------------------------------------------------------------------------------- /demo.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | # comment-only line 9 | 10 | .text 11 | 12 | .set constant42, 42 13 | 14 | textstart: ld r0, r1, 0 # a comment! 15 | st r0, r1, 0 // another comment! 16 | add r0, r1, r2 17 | add r0, r1, 42 18 | sub r0, r1, r2 19 | sub r0, r1, 42 20 | and r0, r1, r2 21 | and r0, r1, 42 22 | or r0, r1, r2 23 | or r0, r1, 42 24 | lsh r0, r1, r2 25 | lsh r0, r1, 42 26 | rsh r0, r1, r2 27 | rsh r0, r1, 42 28 | move r0, r1 29 | move r0, 42 30 | move r0, textstart # moves abs addr of textstart to r0 31 | move r0, constant42 32 | stage_rst 33 | stage_inc 42 34 | stage_dec 23 35 | 36 | rel_b: jumpr -1, 42, lt 37 | jumpr rel_b, 42, LT 38 | jumpr rel_f, 23, ge 39 | rel_f: jumpr +1, 23, GE 40 | jump textstart 41 | jump 0, eq 42 | jump 0, OV 43 | jump r0 44 | jump r0, EQ 45 | jump r0, ov 46 | jumps -1, 42, lt 47 | jumps +1, 23, GT 48 | jumps 0, 0xAD, Eq 49 | 50 | reg_rd 0x3ff48000, 7, 0 51 | reg_wr 0x3ff48000, 7, 0, 42 52 | 53 | i2c_rd 0x10, 7, 0, 0 54 | i2c_wr 0x23, 0x42, 7, 0, 1 55 | 56 | adc r0, 0, 1 57 | 58 | tsens r0, 42 59 | 60 | nop 61 | wait 1000 62 | wake 63 | sleep 1 64 | halt 65 | textend: 66 | 67 | .data 68 | data0: .skip 4, 0x23 69 | data1: .space 4, 0x42 70 | data2: .skip 4 71 | dataw: .word 1, 2, 3, 4 72 | datal: .long 1, 2, 3, 4 73 | datab: .byte 1, 2, 3 # test alignment / fill up of section 74 | dataend: 75 | 76 | .bss 77 | bss0: .skip 4 78 | bss1: .skip 2 # test alignment / fill up of section 79 | bssend: 80 | -------------------------------------------------------------------------------- /demo.sh: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | micropython -m esp32_ulp demo.S 9 | -------------------------------------------------------------------------------- /docs/disassembler.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Disassembler 3 | ===================== 4 | 5 | micropython-esp32-ulp contains a disassembler for disassembling code for the 6 | ESP32 ULP (Ultra Low-Power) Co-Processor. 7 | 8 | The main purpose of this tool is to inspect what instructions our assembler 9 | created, what value each field is set to, and to compare this with the output 10 | created by the assembler from Espressif (part of their `binutils-gdb fork `_), 11 | which we use as our reference implementation. 12 | 13 | 14 | Usage 15 | ------------------------ 16 | 17 | To disassemble a ULP binary, simply run: 18 | 19 | .. code-block:: bash 20 | 21 | micropython -m tools.disassemble path/to/binary.ulp 22 | 23 | You can also specify additional options to ``disassemble.py`` as follows: 24 | 25 | +--------------------------+----------------------------------------------------------------+ 26 | | Option | Description | 27 | +==========================+================================================================+ 28 | | ``-c`` or ``--mcpu`` | Choose ULP variant: either esp32 or esp32s2 | 29 | +--------------------------+----------------------------------------------------------------+ 30 | | ``-h`` | Show help text | 31 | +--------------------------+----------------------------------------------------------------+ 32 | || ``-m `` || Disassemble a provided sequence of hex bytes | 33 | || || (in this case any filename specified is ignored) | 34 | +--------------------------+----------------------------------------------------------------+ 35 | | ``-v`` | Verbose mode (shows ULP header and fields of each instruction) | 36 | +--------------------------+----------------------------------------------------------------+ 37 | 38 | 39 | Disassembling a file 40 | ------------------------ 41 | 42 | The simplest and default mode of the disassembler is to disassemble the 43 | specified file. 44 | 45 | Note that the ULP header is validates and files with unknown magic bytes will be 46 | rejected. The correct 4 magic bytes at the start of a ULP binary are ``ulp\x00``. 47 | 48 | Example disassembling an ESP32 ULP binary: 49 | 50 | .. code-block:: shell 51 | 52 | $ micropython -m tools.disassemble path/to/binary.ulp 53 | .text 54 | 0000 040000d0 LD r0, r1, 0 55 | 0004 0e0000d0 LD r2, r3, 0 56 | 0008 04000068 ST r0, r1, 0 57 | 000c 0b000068 ST r3, r2, 0 58 | .data 59 | 0010 00000000 60 | 61 | Example disassembling an ESP32-S2 ULP binary: 62 | 63 | .. code-block:: shell 64 | 65 | $ micropython -m tools.disassemble -c esp32s2 path/to/binary.ulp 66 | .text 67 | 0000 040000d0 LD r0, r1, 0 68 | 0004 0e0000d0 LD r2, r3, 0 69 | 0008 84010068 ST r0, r1, 0 70 | 000c 8b010068 ST r3, r2, 0 71 | .data 72 | 0010 00000000 73 | 74 | 75 | Disassembling a byte sequence 76 | ----------------------------- 77 | 78 | The ``-m`` option allows disassembling a sequences hex letters representing 79 | ULP instructions. 80 | 81 | This option expects the actual instructions directly, without any ULP header. 82 | 83 | The sequence must contain a number of hex letters exactly divisible by 8, i.e. 84 | 8, 16, 24, etc, because each 32-bit word is made up of 8 hex letters. Spaces 85 | can be included in the sequence and they are ignored. 86 | 87 | The typical use case for this feature is to copy/paste some instructions from 88 | a hexdump (e.g. xxd output) for analysis. 89 | 90 | Example: 91 | 92 | .. code-block:: shell 93 | 94 | # hexdump binary.ulp 95 | $ xxd path/to/binary.ulp 96 | 00000000: 756c 7000 0c00 2400 0400 0000 9300 8074 ulp...$........t 97 | 00000010: 2a80 0488 2004 8074 1c00 0084 0000 0040 *... ..t.......@ 98 | (...) 99 | 100 | # analyse the last 2 instructions 101 | $ micropython -m tools.disassemble -m "1c00 0084 0000 0040" 102 | 0000 1c000084 JUMPS 0, 28, LT 103 | 0004 00000040 NOP 104 | 105 | 106 | Verbose mode 107 | ------------------------ 108 | 109 | In verbose mode the following extra outputs are enabled: 110 | 111 | * ULP header (except when using ``-m``) 112 | * The fields of each instruction and their values 113 | 114 | For example: 115 | 116 | .. code-block:: 117 | 118 | header 119 | ULP magic : b'ulp\x00' (0x00706c75) 120 | .text offset : 12 (0x0c) 121 | .text size : 36 (0x24) 122 | .data offset : 48 (0x30) 123 | .data size : 4 (0x04) 124 | .bss size : 0 (0x00) 125 | ---------------------------------------- 126 | .text 127 | 0000 93008072 MOVE r3, 9 128 | dreg = 3 129 | imm = 9 130 | opcode = 7 131 | sel = 4 (MOV) 132 | sreg = 0 133 | sub_opcode = 1 134 | unused = 0 135 | (...detail truncated...) 136 | 0020 000000b0 HALT 137 | opcode = 11 (0x0b) 138 | unused = 0 139 | ---------------------------------------- 140 | .data 141 | 0000 00000000 142 | 143 | 144 | Disassembling on device 145 | ----------------------------- 146 | 147 | The disassembler also works when used on an ESP32 device. 148 | 149 | To use the disassembler on a real device: 150 | 151 | * ensure ``micropython-esp32-ulp`` is installed on the device (see `docs/index.rst `_). 152 | * upload ``tools/disassemble.py`` ``tools/decode.py`` and ``tools/decode_s2.py`` to the device 153 | (any directory will do, as long as those 3 files are in the same directory) 154 | * the following example code assumes you placed the 3 files into the device's "root" directory 155 | * run the following (note, we must specify which the cpu the binary is for): 156 | 157 | .. code-block:: python 158 | 159 | from disassemble import disassemble_file 160 | # then either: 161 | disassemble_file('path/to/file.ulp', cpu='esp32s2') # normal mode 162 | # or: 163 | disassemble_file('path/to/file.ulp', cpu='esp32s2', verbose=True) # verbose mode 164 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | micropython-esp32-ulp Documentation 2 | =================================== 3 | 4 | .. contents:: Table of Contents 5 | 6 | 7 | Overview 8 | -------- 9 | 10 | `README.rst `_ gives a general overview of this project. 11 | 12 | 13 | Installation 14 | ------------ 15 | 16 | On the ESP32, install using mip (or upip on older MicroPythons): 17 | 18 | .. code-block:: python 19 | 20 | # step 1: ensure the ESP32 is connected to a network with internet connectivity 21 | 22 | # step 2 (for MicroPython 1.20 or newer) 23 | import mip 24 | mip.install('github:micropython/micropython-esp32-ulp') 25 | 26 | # step 2 (for MicroPython older than 1.20) 27 | import upip 28 | upip.install('micropython-esp32-ulp') 29 | 30 | On a PC, simply ``git clone`` this repo. 31 | 32 | 33 | Getting started 34 | --------------- 35 | 36 | On the ESP32 37 | ++++++++++++ 38 | 39 | The simplest example to try on the ESP32 is `counter.py `_. 40 | It shows how to assemble code, load and run the resulting binary and exchange 41 | data between the ULP and the main CPU. 42 | 43 | Run the ``counter.py`` example: 44 | 45 | 1. Install micropython-esp32-ulp onto the ESP32 as shown above 46 | 2. Upload the `examples/counter.py `_ file to the ESP32 47 | 3. Run with ``import counter`` 48 | 49 | You can also try the `blink.py `_ example, which shows how to 50 | let the ULP blink an LED. 51 | 52 | Look inside each example for a more detailed description. 53 | 54 | 55 | On a PC 56 | +++++++ 57 | 58 | On a PC with the unix port of MicroPython, you can assemble source code as 59 | follows: 60 | 61 | .. code-block:: shell 62 | 63 | git clone https://github.com/micropython/micropython-esp32-ulp.git 64 | cd micropython-esp32-ulp 65 | micropython -m esp32_ulp path/to/code.S # this results in path/to/code.ulp 66 | 67 | The assembler supports selecting a CPU to assemble for using the ``-c`` option 68 | (valid cpu's are ``esp32`` and ``esp32s2``): 69 | 70 | .. code-block:: shell 71 | 72 | micropython -m esp32_ulp -c esp32s2 path/to/code.S # assemble for an ESP32-S2 73 | 74 | 75 | More examples 76 | +++++++++++++ 77 | 78 | Other ULP examples from around the web: 79 | 80 | * https://github.com/espressif/esp-iot-solution/tree/master/examples/ulp_examples 81 | * https://github.com/duff2013/ulptool 82 | * https://github.com/joba-1/Blink-ULP/blob/master/main/ulp/ 83 | 84 | 85 | Advanced usage 86 | -------------- 87 | 88 | In some applications you might want to separate the assembly stage from the 89 | loading/running stage, to avoid having to assemble the code on every startup. 90 | This can be useful in battery-powered applications where every second of sleep 91 | time matters. 92 | 93 | Splitting the assembly and load stage can be combined with other techniques, 94 | for example to implement a caching mechansim for the ULP binary that 95 | automatically updates the binary every time the assembly source code changes. 96 | 97 | The ``esp32_ulp.assemble_file`` function can be used to assemble and link an 98 | assembly source file into a machine code binary file with a ``.ulp`` extension. 99 | That file can then be loaded directly without assembling the source again. 100 | 101 | 1. Create/upload an assembly source file and run the following to get a 102 | loadable ULP binary as a ``.ulp`` file (specify ``cpu='esp32s2'`` if you 103 | have an ESP32-S2 or ESP32-S3 device): 104 | 105 | .. code-block:: python 106 | 107 | import esp32_ulp 108 | esp32_ulp.assemble_file('code.S', cpu='esp32') # this results in code.ulp 109 | 110 | 2. The above prints out the offsets of all global symbols/labels. For the next 111 | step, you will need to note down the offset of the label, which represents 112 | the entry point to your code. 113 | 114 | 3. Now load and run the resulting binary as follows: 115 | 116 | .. code-block:: python 117 | 118 | from esp32 import ULP 119 | 120 | ulp = ULP() 121 | with open('test.ulp', 'rb') as f: 122 | # load the binary into RTC memory 123 | ulp.load_binary(0, f.read()) 124 | 125 | # configure how often the ULP should wake up 126 | ulp.set_wakeup_period(0, 500000) # 500k usec == 0.5 sec 127 | 128 | # start the ULP 129 | # assemble_file printed offsets in number of 32-bit words. 130 | # ulp.run() expects an offset in number of bytes. 131 | # Thus, multiply the offset to our entry point by 4. 132 | # e.g. for an offset of 2: 133 | # 2 words * 4 = 8 bytes 134 | ulp.run(2*4) # specify the offset of the entry point label 135 | 136 | To update the binary every time the source code changes, you would need a 137 | mechanism to detect that the source code changed. This could trigger a re-run 138 | of the ``assemble_file`` function to update the binary. Manually re-running 139 | this function as needed would also work. 140 | 141 | 142 | Preprocessor 143 | ------------ 144 | 145 | There is a simple preprocessor that understands just enough to allow assembling 146 | ULP source files containing convenience macros such as WRITE_RTC_REG. This is 147 | especially useful for assembling ULP examples from Espressif or other ULP code 148 | found as part of Arduino/ESP-IDF projects. 149 | 150 | The preprocessor and how to use it is documented here: `Preprocessor support `_. 151 | 152 | 153 | Disassembler 154 | ------------ 155 | There is a disassembler for disassembling ULP binary code. This is mainly used to 156 | inspect what instructions our assembler created, however it can be used to analyse 157 | any ULP binaries. 158 | 159 | The disassembler and how to use it is documented here: `Disassembler `_. 160 | 161 | 162 | Limitations 163 | ----------- 164 | 165 | Currently the following are not supported: 166 | 167 | * assembler macros using ``.macro`` 168 | * preprocessor macros using ``#define A(x,y) ...`` 169 | * including files using ``#include`` 170 | 171 | 172 | Testing 173 | ------- 174 | 175 | There are unit tests and also compatibility tests that check whether the binary 176 | output is identical with what Espressif's esp32-elf-as (from their `binutils-gdb fork 177 | `_) produces. 178 | 179 | micropython-esp32-ulp has been tested on the Unix port of MicroPython and on real ESP32 180 | devices with the chip type ESP32D0WDQ6 (revision 1) without SPIRAM as well as ESP32-S2 181 | (ESP32-S2FH4) and ESP32-S3 (ESP32-S3R8) devices. 182 | 183 | Consult the Github Actions `workflow definition file `_ 184 | for how to run the different tests. 185 | 186 | 187 | Links 188 | ----- 189 | 190 | Espressif documentation: 191 | 192 | * `ESP32 ULP coprocessor instruction set `_ 193 | * `ESP32 Technical Reference Manual `_ 194 | 195 | GNU Assembler "as" documentation (we try to be compatible for all features that are implemented) 196 | 197 | * `GNU Assembler manual `_ 198 | -------------------------------------------------------------------------------- /docs/preprocess.rst: -------------------------------------------------------------------------------- 1 | ===================== 2 | Preprocessor 3 | ===================== 4 | 5 | micropython-esp32-ulp contains a small preprocessor, which aims to fulfill one goal: 6 | facilitate assembling of ULP code from Espressif and other open-source 7 | projects to loadable/executable machine code without modification. 8 | 9 | Such code uses convenience macros (``READ_RTC_*`` and ``WRITE_RTC_*``) 10 | provided by the ESP-IDF framework, along with constants defined in the 11 | framework's include files (such as ``RTC_GPIO_IN_REG``), to make reading 12 | and writing from/to peripheral registers much easier. 13 | 14 | In order to do this the preprocessor has two capabilities: 15 | 16 | 1. Parse and replace identifiers defined with ``#define`` 17 | 2. Recognise the ``WRITE_RTC_*`` and ``READ_RTC_*`` macros and expand 18 | them in a way that mirrors what the real ESP-IDF macros do. 19 | 20 | 21 | Usage 22 | ------------------------ 23 | 24 | Normally the assembler is called as follows 25 | 26 | .. code-block:: python 27 | 28 | src = "..full assembler file contents" 29 | assembler = Assembler() 30 | assembler.assemble(src) 31 | ... 32 | 33 | With the preprocessor, simply pass the source code via the preprocessor first: 34 | 35 | .. code-block:: python 36 | 37 | from preprocess import preprocess 38 | 39 | src = "..full assembler file contents" 40 | src = preprocess(src) 41 | assembler = Assembler() 42 | assembler.assemble(src) 43 | ... 44 | 45 | 46 | Using a "Defines Database" 47 | -------------------------- 48 | 49 | Because the micropython-esp32-ulp assembler was built for running on the ESP32 50 | microcontroller with limited RAM, the preprocessor aims to work there too. 51 | 52 | To handle large number of defined constants (such as the ``RTC_*`` constants from 53 | the ESP-IDF) the preprocessor can use a database (based on BerkleyDB) stored on the 54 | device's filesystem for looking up defines. 55 | 56 | The database needs to be populated before preprocessing. (Usually, when only using 57 | constants from the ESP-IDF, this is a one-time step, because the include files 58 | don't change.) The database can be reused for all subsequent preprocessor runs. 59 | 60 | (The database can also be generated on a PC and then deployed to the ESP32, to 61 | save processing effort on the device. In that case the include files themselves 62 | are not needed on the device either.) 63 | 64 | 1. Build the defines database 65 | 66 | The ``esp32_ulp.parse_to_db`` tool can be used to generate the defines 67 | database from include files. The resulting file will be called 68 | ``defines.db``. 69 | 70 | (The following assume running on a PC. To do this on device, refer to the 71 | `esp32_ulp/parse_to_db.py <../esp32_ulp/parse_to_db.py>`_ file.) 72 | 73 | .. code-block:: bash 74 | 75 | # general command 76 | micropython -m esp32_ulp.parse_to_db path/to/include.h 77 | 78 | # loading specific ESP-IDF include files 79 | micropython -m esp32_ulp.parse_to_db esp-idf/components/soc/esp32/include/soc/soc_ulp.h 80 | 81 | # loading multiple files at once 82 | micropython -m esp32_ulp.parse_to_db esp-idf/components/soc/esp32/include/soc/*.h 83 | 84 | # if file system space is not a concern, the following can be convenient 85 | # by including all relevant include files from the ESP-IDF framework. 86 | # This results in an approximately 2MB large database. 87 | micropython -m esp32_ulp.parse_to_db \ 88 | esp-idf/components/soc/esp32/include/soc/*.h \ 89 | esp-idf/components/esp_common/include/*.h 90 | 91 | # most ULP code uses only 5 include files. Parsing only those into the 92 | # database should thus allow assembling virtually all ULP code one would 93 | # find or want to write. 94 | # This results in an approximately 250kB large database. 95 | micropython -m esp32_ulp.parse_to_db \ 96 | esp-idf/components/soc/esp32/include/soc/{soc,soc_ulp,rtc_cntl_reg,rtc_io_reg,sens_reg}.h 97 | 98 | 99 | .. warning:: 100 | 101 | `:warning:` Ensure that you include the header files for the correct 102 | variant you are working with. In the example code above, simply switch 103 | ``esp32`` to ``esp32s2`` or ``esp32s3`` in the path to the include files. 104 | 105 | There are subtle differences across the ESP32 variants such as which 106 | constants are available or the value of certain constants. For example, 107 | peripheral register addresses differ between the 3 variants even though 108 | many constants for peripheral registers are available on all 3 variants. 109 | Other constants such as those relating to the HOLD functionality of touch 110 | pads are only available on the original ESP32. 111 | 112 | 113 | 2. Using the defines database during preprocessing 114 | 115 | The preprocessor will automatically use a defines database, when using the 116 | ``preprocess.preprocess`` convenience function, even when the database does 117 | not exist (an absent database is treated like an empty database, and care 118 | is taken not to create an empty database file, cluttering up the filesystem, 119 | when not needed). 120 | 121 | If you do not want the preprocessor use use a DefinesDB, pass ``False`` to 122 | the ``use_defines_db`` argument of the ``preprocess`` convenience function, 123 | or instantiate the ``Preprocessor`` class directly, without passing it a 124 | DefinesDB instance via ``use_db``. 125 | 126 | 127 | Design choices 128 | -------------- 129 | 130 | The preprocessor does not support: 131 | 132 | 1. Function style macros such as :code:`#define f(a,b) (a+b)` 133 | 134 | This is not important, because there are only few RTC macros that need 135 | to be supported and they are simply implemented as Python functions. 136 | 137 | Since the preprocessor will understand ``#define`` directives directly in the 138 | assembler source file, include mechanisms are not needed in some cases 139 | (simply copying the needed ``#define`` statements from include files into the 140 | assembler source will work). 141 | 142 | 2. ``#include`` directives 143 | 144 | The preprocessor does not currently follow ``#include`` directives. To 145 | limit space requirements (both in memory and on the filesystem), the 146 | preprocessor relies on a database of defines (key/value pairs). This 147 | database should be populated before using the preprocessor, by using the 148 | ``esp32_ulp.parse_to_db`` tool (see section above), which parses include 149 | files for identifiers defined therein. 150 | 151 | 3. Preserving comments 152 | 153 | The assumption is that the output will almost always go into the 154 | assembler directly, so preserving comments is not very useful and 155 | would add a lot of complexity. 156 | -------------------------------------------------------------------------------- /esp32_ulp/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from .util import garbage_collect 9 | 10 | from .preprocess import preprocess 11 | from .assemble import Assembler 12 | from .link import make_binary 13 | garbage_collect('after import') 14 | 15 | 16 | def src_to_binary_ext(src, cpu): 17 | assembler = Assembler(cpu) 18 | src = preprocess(src) 19 | assembler.assemble(src, remove_comments=False) # comments already removed by preprocessor 20 | garbage_collect('before symbols export') 21 | addrs_syms = assembler.symbols.export() 22 | text, data, bss_len = assembler.fetch() 23 | return make_binary(text, data, bss_len), addrs_syms 24 | 25 | 26 | def src_to_binary(src, cpu): 27 | binary, addrs_syms = src_to_binary_ext(src, cpu) 28 | for addr, sym in addrs_syms: 29 | print('%04d %s' % (addr, sym)) 30 | return binary 31 | 32 | 33 | def assemble_file(filename, cpu): 34 | with open(filename) as f: 35 | src = f.read() 36 | 37 | binary = src_to_binary(src, cpu) 38 | 39 | if filename.endswith('.s') or filename.endswith('.S'): 40 | filename = filename[:-2] 41 | with open(filename + '.ulp', 'wb') as f: 42 | f.write(binary) 43 | 44 | -------------------------------------------------------------------------------- /esp32_ulp/__main__.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | import sys 9 | from . import assemble_file 10 | 11 | 12 | def main(fn, cpu): 13 | assemble_file(fn, cpu) 14 | 15 | 16 | if __name__ == '__main__': 17 | cpu = 'esp32' 18 | filename = sys.argv[1] 19 | if len(sys.argv) > 3: 20 | if sys.argv[1] in ('-c', '--mcpu'): 21 | cpu = sys.argv[2].lower() 22 | if cpu not in ('esp32', 'esp32s2'): 23 | raise ValueError('Invalid cpu') 24 | filename = sys.argv[3] 25 | main(filename, cpu) 26 | 27 | -------------------------------------------------------------------------------- /esp32_ulp/assemble.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | ESP32 ULP Co-Processor Assembler 10 | """ 11 | 12 | import re 13 | from .nocomment import remove_comments as do_remove_comments 14 | from .util import garbage_collect 15 | 16 | TEXT, DATA, BSS = 'text', 'data', 'bss' 17 | 18 | REL, ABS = 0, 1 19 | 20 | 21 | class SymbolTable: 22 | def __init__(self, symbols, bases, globals): 23 | self._symbols = symbols 24 | self._bases = bases 25 | self._globals = globals 26 | 27 | def set_bases(self, bases): 28 | self._bases = bases 29 | 30 | def set_from(self, from_section, from_offset): 31 | self._from_section, self._from_offset = from_section, from_offset 32 | 33 | def get_from(self): 34 | return self._from_section, self._from_offset 35 | 36 | def set_sym(self, symbol, stype, section, value): 37 | entry = (stype, section, value) 38 | if symbol in self._symbols and entry != self._symbols[symbol]: 39 | raise Exception('redefining symbol %s with different value %r -> %r.' % (symbol, self._symbols[symbol], entry)) 40 | self._symbols[symbol] = entry 41 | 42 | def has_sym(self, symbol): 43 | return symbol in self._symbols 44 | 45 | def get_sym(self, symbol): 46 | entry = self._symbols[symbol] 47 | return entry 48 | 49 | def dump(self): 50 | for symbol, entry in self._symbols.items(): 51 | print(symbol, entry) 52 | 53 | def export(self, incl_non_globals=False): 54 | addrs_syms = [(self.resolve_absolute(entry), symbol) 55 | for symbol, entry in self._symbols.items() 56 | if incl_non_globals or symbol in self._globals] 57 | return sorted(addrs_syms) 58 | 59 | def to_abs_addr(self, section, offset): 60 | base = self._bases[section] 61 | return base + offset 62 | 63 | def resolve_absolute(self, symbol): 64 | if isinstance(symbol, str): 65 | stype, section, value = self.get_sym(symbol) 66 | elif isinstance(symbol, tuple): 67 | stype, section, value = symbol 68 | else: 69 | raise TypeError 70 | if stype == REL: 71 | return self.to_abs_addr(section, value) 72 | if stype == ABS: 73 | return value 74 | raise TypeError(stype) 75 | 76 | def resolve_relative(self, symbol): 77 | if isinstance(symbol, str): 78 | sym_type, sym_section, sym_value = self.get_sym(symbol) 79 | elif isinstance(symbol, tuple): 80 | sym_type, sym_section, sym_value = symbol 81 | else: 82 | raise TypeError 83 | if sym_type == REL: 84 | sym_addr = self.to_abs_addr(sym_section, sym_value) 85 | elif sym_type == ABS: 86 | sym_addr = sym_value 87 | from_addr = self.to_abs_addr(self._from_section, self._from_offset) 88 | return sym_addr - from_addr 89 | 90 | def set_global(self, symbol): 91 | self._globals[symbol] = True 92 | pass 93 | 94 | 95 | class Assembler: 96 | 97 | def __init__(self, cpu='esp32', symbols=None, bases=None, globals=None): 98 | if cpu == 'esp32': 99 | opcode_module = 'opcodes' 100 | elif cpu == 'esp32s2': 101 | opcode_module = 'opcodes_s2' 102 | else: 103 | raise ValueError('Invalid cpu') 104 | 105 | relative_import = 1 if '/' in __file__ else 0 106 | self.opcodes = __import__(opcode_module, None, None, [], relative_import) 107 | 108 | self.symbols = SymbolTable(symbols or {}, bases or {}, globals or {}) 109 | self.opcodes.symbols = self.symbols # XXX dirty hack 110 | 111 | # regex for parsing assembly lines 112 | # format: [[whitespace]label:][whitespace][opcode[whitespace arg[,arg...]]] 113 | # where [] means optional 114 | # initialised here once, instead of compiling once per line 115 | self.line_regex = re.compile(r'^(\s*([a-zA-Z0-9_$.]+):)?\s*((\S*)\s*(.*))$') 116 | 117 | def init(self, a_pass): 118 | self.a_pass = a_pass 119 | self.sections = dict(text=[], data=[]) 120 | self.offsets = dict(text=0, data=0, bss=0) 121 | self.section = TEXT 122 | 123 | def parse_line(self, line): 124 | """ 125 | parse one line of assembler into label, opcode, args. 126 | comments already have been removed by pre-processing. 127 | 128 | a line looks like (label, opcode, args, comment are all optional): 129 | 130 | label: opcode arg1, arg2, ... 131 | """ 132 | if not line: 133 | return 134 | 135 | matches = self.line_regex.match(line) 136 | label, opcode, args = matches.group(2), matches.group(4), matches.group(5) 137 | 138 | label = label if label else None # force empty strings to None 139 | opcode = opcode if opcode else None # force empty strings to None 140 | args = tuple(arg.strip() for arg in args.split(',')) if args else () 141 | 142 | return label, opcode, args 143 | 144 | def split_statements(self, lines): 145 | for line in lines: 146 | for statement in line.split(';'): 147 | yield statement.rstrip() 148 | 149 | def parse(self, lines): 150 | parsed = [self.parse_line(line) for line in self.split_statements(lines)] 151 | return [p for p in parsed if p is not None] 152 | 153 | 154 | def append_section(self, value, expected_section=None): 155 | s = self.section 156 | if expected_section is not None and s is not expected_section: 157 | raise TypeError('only allowed in %s section' % expected_section) 158 | if s is BSS: 159 | if int.from_bytes(value, 'little') != 0: 160 | raise ValueError('attempt to store non-zero value in section .bss') 161 | # just increase BSS size by length of value 162 | self.offsets[s] += len(value) 163 | else: 164 | self.sections[s].append(value) 165 | self.offsets[s] += len(value) 166 | 167 | def finalize_sections(self): 168 | # make sure all sections have a bytelength dividable by 4, 169 | # thus having all sections aligned at 32bit-word boundaries. 170 | for s in list(self.sections.keys()) + [BSS, ]: 171 | offs = self.offsets[s] 172 | mod = offs % 4 173 | if mod: 174 | fill = int(0).to_bytes(4 - mod, 'little') 175 | self.offsets[s] += len(fill) 176 | if s is not BSS: 177 | self.sections[s].append(fill) 178 | 179 | def compute_bases(self): 180 | bases = {} 181 | addr = 0 182 | # lay out sections in this order: 183 | for s in [TEXT, DATA, BSS]: # TODO: more flexibility for custom sections 184 | bases[s] = addr 185 | addr += self.offsets[s] // 4 # 32bit word addresses 186 | return bases 187 | 188 | def dump(self): 189 | print("Symbols:") 190 | self.symbols.dump() 191 | print("%s section:" % TEXT) 192 | for t in self.sections[TEXT]: 193 | print("%08x" % int.from_bytes(t, 'little')) 194 | print("size: %d" % self.offsets[TEXT]) 195 | print("%s section:" % DATA) 196 | for d in self.sections[DATA]: 197 | print("%08x" % int.from_bytes(d, 'little')) 198 | print("size: %d" % self.offsets[DATA]) 199 | print("%s section:" % BSS) 200 | print("size: %d" % self.offsets[BSS]) 201 | 202 | def fetch(self): 203 | def get_bytes(section): 204 | return b''.join(self.sections[section]) 205 | 206 | return get_bytes(TEXT), get_bytes(DATA), self.offsets[BSS] 207 | 208 | def d_text(self): 209 | self.section = TEXT 210 | 211 | def d_data(self): 212 | self.section = DATA 213 | 214 | def d_bss(self): 215 | self.section = BSS 216 | 217 | def fill(self, section, amount, fill_byte): 218 | if fill_byte is not None and section is BSS: 219 | raise ValueError('fill in bss section not allowed') 220 | if section is TEXT: # TODO: text section should be filled with NOPs 221 | raise ValueError('fill/skip/align in text section not supported') 222 | fill = int(fill_byte or 0).to_bytes(1, 'little') * amount 223 | self.offsets[section] += len(fill) 224 | if section is not BSS: 225 | self.sections[section].append(fill) 226 | 227 | def d_skip(self, amount, fill=None): 228 | amount = int(amount) 229 | self.fill(self.section, amount, fill) 230 | 231 | d_space = d_skip 232 | 233 | def d_align(self, align=4, fill=None): 234 | align = int(align) 235 | offs = self.offsets[self.section] 236 | mod = offs % align 237 | if mod: 238 | amount = align - mod 239 | self.fill(self.section, amount, fill) 240 | 241 | def d_set(self, symbol, expr): 242 | value = int(self.opcodes.eval_arg(expr)) 243 | self.symbols.set_sym(symbol, ABS, None, value) 244 | 245 | def d_global(self, symbol): 246 | self.symbols.set_global(symbol) 247 | 248 | def append_data(self, wordlen, args): 249 | data = [int(arg).to_bytes(wordlen, 'little') for arg in args] 250 | self.append_section(b''.join(data)) 251 | 252 | def d_byte(self, *args): 253 | self.append_data(1, args) 254 | 255 | def d_word(self, *args): 256 | self.append_data(2, args) 257 | 258 | def d_long(self, *args): 259 | self.d_int(*args) 260 | 261 | def d_int(self, *args): 262 | # .long and .int are identical as per GNU assembler documentation 263 | # https://sourceware.org/binutils/docs/as/Long.html 264 | self.append_data(4, args) 265 | 266 | def assembler_pass(self, lines): 267 | for label, opcode, args in self.parse(lines): 268 | self.symbols.set_from(self.section, self.offsets[self.section] // 4) 269 | if label is not None: 270 | self.symbols.set_sym(label, REL, *self.symbols.get_from()) 271 | if opcode is not None: 272 | if opcode[0] == '.': 273 | # assembler directive 274 | func = getattr(self, 'd_' + opcode[1:]) 275 | if func is not None: 276 | result = func(*args) 277 | if result is not None: 278 | self.append_section(result) 279 | continue 280 | else: 281 | # machine instruction 282 | opcode_lower = opcode.lower() 283 | func = getattr(self.opcodes, 'i_' + opcode_lower, None) 284 | if func is not None: 285 | if self.a_pass == 1: 286 | # during the first pass, symbols are not all known yet. 287 | # so we add empty instructions to the section, to determine 288 | # section sizes and symbol offsets for pass 2. 289 | result = (0,) * self.opcodes.no_of_instr(opcode_lower, args) 290 | else: 291 | result = func(*args) 292 | 293 | if not isinstance(result, tuple): 294 | result = (result,) 295 | 296 | for instruction in result: 297 | self.append_section(instruction.to_bytes(4, 'little'), TEXT) 298 | continue 299 | raise ValueError('Unknown opcode or directive: %s' % opcode) 300 | self.finalize_sections() 301 | 302 | def assemble(self, text, remove_comments=True): 303 | lines = do_remove_comments(text) if remove_comments else text.splitlines() 304 | self.init(1) # pass 1 is only to get the symbol table right 305 | self.assembler_pass(lines) 306 | self.symbols.set_bases(self.compute_bases()) 307 | garbage_collect('before pass2') 308 | self.init(2) # now we know all symbols and bases, do the real assembler pass, pass 2 309 | self.assembler_pass(lines) 310 | garbage_collect('after pass2') 311 | 312 | -------------------------------------------------------------------------------- /esp32_ulp/definesdb.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | import os 9 | import btree 10 | from .util import file_exists 11 | 12 | DBNAME = 'defines.db' 13 | 14 | 15 | class DefinesDB: 16 | def __init__(self): 17 | self._file = None 18 | self._db = None 19 | self._db_exists = None 20 | 21 | def clear(self): 22 | self.close() 23 | try: 24 | os.remove(DBNAME) 25 | self._db_exists = False 26 | except OSError: 27 | pass 28 | 29 | def is_open(self): 30 | return self._db is not None 31 | 32 | def open(self): 33 | if self.is_open(): 34 | return 35 | try: 36 | self._file = open(DBNAME, 'r+b') 37 | except OSError: 38 | self._file = open(DBNAME, 'w+b') 39 | self._db = btree.open(self._file) 40 | self._db_exists = True 41 | 42 | def close(self): 43 | if not self.is_open(): 44 | return 45 | self._db.close() 46 | self._db = None 47 | self._file.close() 48 | self._file = None 49 | 50 | def db_exists(self): 51 | if self._db_exists is None: 52 | self._db_exists = file_exists(DBNAME) 53 | return self._db_exists 54 | 55 | def update(self, dictionary): 56 | for k, v in dictionary.items(): 57 | self.__setitem__(k, v) 58 | 59 | def get(self, key, default): 60 | try: 61 | result = self.__getitem__(key) 62 | except KeyError: 63 | result = default 64 | return result 65 | 66 | def keys(self): 67 | if not self.db_exists(): 68 | return [] 69 | 70 | self.open() 71 | return [k.decode() for k in self._db.keys()] 72 | 73 | def __getitem__(self, key): 74 | if not self.db_exists(): 75 | raise KeyError 76 | 77 | self.open() 78 | return self._db[key.encode()].decode() 79 | 80 | def __setitem__(self, key, value): 81 | self.open() 82 | self._db[key.encode()] = str(value).encode() 83 | 84 | def __iter__(self): 85 | return iter(self.keys()) 86 | -------------------------------------------------------------------------------- /esp32_ulp/link.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from uctypes import struct, addressof, LITTLE_ENDIAN, UINT16, UINT32 9 | 10 | 11 | def make_binary(text, data, bss_size): 12 | if not isinstance(text, bytes): 13 | raise TypeError('text section must be binary bytes') 14 | if not isinstance(data, bytes): 15 | raise TypeError('data section must be binary bytes') 16 | binary_header_struct_def = dict( 17 | magic = 0 | UINT32, 18 | text_offset = 4 | UINT16, 19 | text_size = 6 | UINT16, 20 | data_size = 8 | UINT16, 21 | bss_size = 10 | UINT16, 22 | ) 23 | header = bytearray(12) 24 | h = struct(addressof(header), binary_header_struct_def, LITTLE_ENDIAN) 25 | # https://github.com/espressif/esp-idf/blob/master/components/ulp/ld/esp32.ulp.ld 26 | # ULP program binary should have the following format (all values little-endian): 27 | h.magic = 0x00706c75 # (4 bytes) 28 | h.text_offset = 12 # offset of .text section from binary start (2 bytes) 29 | h.text_size = len(text) # size of .text section (2 bytes) 30 | h.data_size = len(data) # size of .data section (2 bytes) 31 | h.bss_size = bss_size # size of .bss section (2 bytes) 32 | return bytes(header) + text + data 33 | 34 | -------------------------------------------------------------------------------- /esp32_ulp/nocomment.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | def remove_comments(s): 9 | """ 10 | Remove comments of these styles: 11 | 12 | CHASH: # comment python style, up to: EOL 13 | CSLASHSLASH: // comment C style, up to: EOL 14 | CSLASHSTAR: /* comment C style (single/multi line), up to: */ 15 | 16 | Strings can be like 'strings' or "strings". 17 | Any comment-starting chars within strings are not considered. 18 | Escaping of (string-end) chars via backslash in strings is considered. 19 | 20 | Also, leading and trailing whitespace is removed (after comment removal). 21 | Indented lines are re-indented afterwards with a single tab char. 22 | 23 | Line numbers stay as in input file because empty lines are kept. 24 | 25 | s: string with comments (can include newlines) 26 | returns: list of text lines 27 | """ 28 | # note: micropython's ure module was not capable enough to process this: 29 | # missing methods, re modes, recursion limit exceeded, ... 30 | # simpler hacks also didn't seem powerful enough to address all the 31 | # corner cases of CSLASHSTAR vs. *STR, so this state machine came to life: 32 | SRC, CHASH, CSLASHSLASH, CSLASHSTAR, DSTR, SSTR = range(6) # states 33 | 34 | line = [] # collect chars of one line 35 | lines = [] # collect result lines 36 | 37 | def finish_line(): 38 | # assemble a line from characters, try to get rid of trailing and 39 | # most of leading whitespace (keep/put one tab for indented lines). 40 | nonlocal line 41 | line = ''.join(line) 42 | is_indented = line.startswith(' ') or line.startswith('\t') 43 | line = line.strip() 44 | if line and is_indented: 45 | line = '\t' + line 46 | lines.append(line) 47 | line = [] 48 | 49 | state = SRC 50 | i = 0 51 | length = len(s) 52 | while i < length: 53 | c = s[i] 54 | cn = s[i + 1] if i + 1 < length else '\0' 55 | if state == SRC: 56 | if c == '#': # starting to-EOL comment 57 | state = CHASH 58 | i += 1 59 | elif c == '/': 60 | if cn == '/': # starting to-EOL comment 61 | state = CSLASHSLASH 62 | i += 2 63 | elif cn == '*': # starting a /* comment 64 | state = CSLASHSTAR 65 | i += 2 66 | else: 67 | i += 1 68 | line.append(c) 69 | elif c == '"': 70 | state = DSTR 71 | i += 1 72 | line.append(c) 73 | elif c == "'": 74 | state = SSTR 75 | i += 1 76 | line.append(c) 77 | elif c == '\n': 78 | i += 1 79 | finish_line() 80 | else: 81 | i += 1 82 | line.append(c) 83 | elif state == CHASH or state == CSLASHSLASH: 84 | if c != '\n': # comment runs until EOL 85 | i += 1 86 | else: 87 | state = SRC 88 | i += 1 89 | finish_line() 90 | elif state == CSLASHSTAR: 91 | if c == '*' and cn == '/': # ending a comment */ 92 | state = SRC 93 | i += 2 94 | elif c == '\n': 95 | i += 1 96 | finish_line() 97 | else: 98 | i += 1 99 | elif state == DSTR and c == '"' or state == SSTR and c == "'": # string end 100 | state = SRC 101 | i += 1 102 | line.append(c) 103 | elif state == DSTR or state == SSTR: 104 | i += 1 105 | line.append(c) 106 | if c == '\\': # escaping backslash 107 | i += 1 # do not look at char after the backslash 108 | line.append(cn) 109 | else: 110 | raise Exception("state: %d c: %s cn: %s" % (state, c, cn)) 111 | if line: 112 | # no final \n triggered processing these chars yet, do it now 113 | finish_line() 114 | return lines 115 | 116 | 117 | if __name__ == '__main__': 118 | import sys 119 | filename = sys.argv[1] 120 | with open(filename, "r") as f: 121 | text = f.read() 122 | lines = remove_comments(text) 123 | with open(filename + ".nocomments", "w") as f: 124 | for line in lines: 125 | f.write(line + '\n') 126 | 127 | -------------------------------------------------------------------------------- /esp32_ulp/parse_to_db.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | import sys 9 | 10 | from .preprocess import Preprocessor 11 | from .definesdb import DefinesDB 12 | 13 | 14 | def parse(files): 15 | db = DefinesDB() 16 | 17 | p = Preprocessor() 18 | p.use_db(db) 19 | 20 | for f in files: 21 | print('Processing file:', f) 22 | 23 | p.process_include_file(f) 24 | 25 | print('Done.') 26 | 27 | 28 | if __name__ == '__main__': 29 | parse(sys.argv[1:]) 30 | 31 | -------------------------------------------------------------------------------- /esp32_ulp/preprocess.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from . import nocomment 9 | from .util import split_tokens 10 | from .definesdb import DefinesDB 11 | 12 | 13 | class RTC_Macros: 14 | @staticmethod 15 | def READ_RTC_REG(rtc_reg, low_bit, bit_width): 16 | return '\treg_rd ' + ', '.join(( 17 | rtc_reg, 18 | '%s + %s - 1' % (low_bit, bit_width), 19 | low_bit 20 | )) 21 | 22 | @staticmethod 23 | def WRITE_RTC_REG(rtc_reg, low_bit, bit_width, value): 24 | return '\treg_wr ' + ', '.join(( 25 | rtc_reg, 26 | '%s + %s - 1' % (low_bit, bit_width), 27 | low_bit, 28 | value 29 | )) 30 | 31 | @staticmethod 32 | def READ_RTC_FIELD(rtc_reg, low_bit): 33 | return RTC_Macros.READ_RTC_REG(rtc_reg, low_bit, 1) 34 | 35 | @staticmethod 36 | def WRITE_RTC_FIELD(rtc_reg, low_bit, value): 37 | return RTC_Macros.WRITE_RTC_REG(rtc_reg, low_bit, 1, value + ' & 1') 38 | 39 | 40 | class Preprocessor: 41 | def __init__(self): 42 | self._defines_db = None 43 | self._defines = {} 44 | 45 | def parse_define_line(self, line): 46 | line = line.strip() 47 | if not line.startswith("#define"): 48 | # skip lines not containing #define 49 | return {} 50 | line = line[8:].strip() # remove #define 51 | parts = line.split(None, 1) 52 | if len(parts) != 2: 53 | # skip defines without value 54 | return {} 55 | identifier, value = parts 56 | tmp = identifier.split('(', 1) 57 | if len(tmp) == 2: 58 | # skip parameterised defines (macros) 59 | return {} 60 | value = "".join(nocomment.remove_comments(value)).strip() 61 | return {identifier: value} 62 | 63 | def parse_defines(self, content): 64 | for line in content.splitlines(): 65 | self._defines.update(self.parse_define_line(line)) 66 | 67 | return self._defines 68 | 69 | def expand_defines(self, line): 70 | found = True 71 | while found: # do as many passed as needed, until nothing was replaced anymore 72 | found = False 73 | tokens = split_tokens(line) 74 | line = "" 75 | for t in tokens: 76 | lu = self._defines.get(t, t) 77 | if lu == t and self._defines_db: 78 | lu = self._defines_db.get(t, t) 79 | if lu == t and t == 'BIT': 80 | # Special hack: BIT(..) translates to a 32-bit mask where only the specified bit is set. 81 | # But the reg_wr and reg_rd opcodes expect actual bit numbers for argument 2 and 3. 82 | # While the real READ_RTC_*/WRITE_RTC_* macros take in the output of BIT(x), they 83 | # ultimately convert these back (via helper macros) to the bit number (x). And since this 84 | # preprocessor does not (aim to) implement "proper" macro-processing, we can simply 85 | # short-circuit this round-trip via macros and replace "BIT" with nothing so that 86 | # "BIT(x)" gets mapped to "(x)". 87 | continue 88 | if lu != t: 89 | found = True 90 | line += lu 91 | 92 | return line 93 | 94 | def process_include_file(self, filename): 95 | with self.open_db() as db: 96 | with open(filename, 'r') as f: 97 | for line in f: 98 | result = self.parse_define_line(line) 99 | db.update(result) 100 | 101 | return db 102 | 103 | def expand_rtc_macros(self, line): 104 | clean_line = line.strip() 105 | if not clean_line: 106 | return line 107 | 108 | macro = clean_line.split('(', 1) 109 | if len(macro) != 2: 110 | return line 111 | 112 | macro_name, macro_args = macro 113 | 114 | macro_fn = getattr(RTC_Macros, macro_name, None) 115 | if macro_fn is None: 116 | return line 117 | 118 | macro_args, _ = macro_args.rsplit(')', 1) # trim away right bracket. safe as comments already stripped 119 | macro_args = macro_args.split(',') # not safe when args contain ',' but we should not have those 120 | macro_args = [x.strip() for x in macro_args] 121 | 122 | return macro_fn(*macro_args) 123 | 124 | def use_db(self, defines_db): 125 | self._defines_db = defines_db 126 | 127 | def open_db(self): 128 | class ctx: 129 | def __init__(self, db): 130 | self._db = db 131 | 132 | def __enter__(self): 133 | # not opening DefinesDB - it opens itself when needed 134 | return self._db 135 | 136 | def __exit__(self, type, value, traceback): 137 | if isinstance(self._db, DefinesDB): 138 | self._db.close() 139 | 140 | if self._defines_db: 141 | return ctx(self._defines_db) 142 | 143 | return ctx(self._defines) 144 | 145 | def preprocess(self, content): 146 | self.parse_defines(content) 147 | 148 | with self.open_db(): 149 | lines = nocomment.remove_comments(content) 150 | result = [] 151 | for line in lines: 152 | line = self.expand_defines(line) 153 | line = self.expand_rtc_macros(line) 154 | result.append(line) 155 | result = "\n".join(result) 156 | 157 | return result 158 | 159 | 160 | def preprocess(content, use_defines_db=True): 161 | preprocessor = Preprocessor() 162 | preprocessor.use_db(DefinesDB()) 163 | return preprocessor.preprocess(content) 164 | -------------------------------------------------------------------------------- /esp32_ulp/soc.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Address / Register definitions for the ESP32 SoC 10 | """ 11 | 12 | # Reference: 13 | # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/reg_base.h 14 | 15 | DR_REG_DPORT_BASE = 0x3ff00000 16 | DR_REG_AES_BASE = 0x3ff01000 17 | DR_REG_RSA_BASE = 0x3ff02000 18 | DR_REG_SHA_BASE = 0x3ff03000 19 | DR_REG_FLASH_MMU_TABLE_PRO = 0x3ff10000 20 | DR_REG_FLASH_MMU_TABLE_APP = 0x3ff12000 21 | DR_REG_DPORT_END = 0x3ff13FFC 22 | DR_REG_UART_BASE = 0x3ff40000 23 | DR_REG_SPI1_BASE = 0x3ff42000 24 | DR_REG_SPI0_BASE = 0x3ff43000 25 | DR_REG_GPIO_BASE = 0x3ff44000 26 | DR_REG_GPIO_SD_BASE = 0x3ff44f00 27 | DR_REG_FE2_BASE = 0x3ff45000 28 | DR_REG_FE_BASE = 0x3ff46000 29 | DR_REG_FRC_TIMER_BASE = 0x3ff47000 30 | DR_REG_RTCCNTL_BASE = 0x3ff48000 31 | DR_REG_RTCIO_BASE = 0x3ff48400 32 | DR_REG_SENS_BASE = 0x3ff48800 33 | DR_REG_RTC_I2C_BASE = 0x3ff48C00 34 | DR_REG_IO_MUX_BASE = 0x3ff49000 35 | DR_REG_HINF_BASE = 0x3ff4B000 36 | DR_REG_UHCI1_BASE = 0x3ff4C000 37 | DR_REG_I2S_BASE = 0x3ff4F000 38 | DR_REG_UART1_BASE = 0x3ff50000 39 | DR_REG_BT_BASE = 0x3ff51000 40 | DR_REG_I2C_EXT_BASE = 0x3ff53000 41 | DR_REG_UHCI0_BASE = 0x3ff54000 42 | DR_REG_SLCHOST_BASE = 0x3ff55000 43 | DR_REG_RMT_BASE = 0x3ff56000 44 | DR_REG_PCNT_BASE = 0x3ff57000 45 | DR_REG_SLC_BASE = 0x3ff58000 46 | DR_REG_LEDC_BASE = 0x3ff59000 47 | DR_REG_EFUSE_BASE = 0x3ff5A000 48 | DR_REG_SPI_ENCRYPT_BASE = 0x3ff5B000 49 | DR_REG_NRX_BASE = 0x3ff5CC00 50 | DR_REG_BB_BASE = 0x3ff5D000 51 | DR_REG_PWM0_BASE = 0x3ff5E000 52 | DR_REG_TIMERGROUP0_BASE = 0x3ff5F000 53 | DR_REG_TIMERGROUP1_BASE = 0x3ff60000 54 | DR_REG_RTCMEM0_BASE = 0x3ff61000 55 | DR_REG_RTCMEM1_BASE = 0x3ff62000 56 | DR_REG_RTCMEM2_BASE = 0x3ff63000 57 | DR_REG_SPI2_BASE = 0x3ff64000 58 | DR_REG_SPI3_BASE = 0x3ff65000 59 | DR_REG_SYSCON_BASE = 0x3ff66000 60 | DR_REG_APB_CTRL_BASE = 0x3ff66000 # Old name for SYSCON, to be removed 61 | DR_REG_I2C1_EXT_BASE = 0x3ff67000 62 | DR_REG_SDMMC_BASE = 0x3ff68000 63 | DR_REG_EMAC_BASE = 0x3ff69000 64 | DR_REG_CAN_BASE = 0x3ff6B000 65 | DR_REG_PWM1_BASE = 0x3ff6C000 66 | DR_REG_I2S1_BASE = 0x3ff6D000 67 | DR_REG_UART2_BASE = 0x3ff6E000 68 | PERIPHS_SPI_ENCRYPT_BASEADDR = DR_REG_SPI_ENCRYPT_BASE 69 | -------------------------------------------------------------------------------- /esp32_ulp/soc_s2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Address / Register definitions for the ESP32-S2 SoC 10 | """ 11 | 12 | # Reference: 13 | # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/reg_base.h 14 | 15 | DR_REG_SYSTEM_BASE = 0x3f4c0000 16 | DR_REG_SENSITIVE_BASE = 0x3f4c1000 17 | DR_REG_INTERRUPT_BASE = 0x3f4c2000 18 | DR_REG_DMA_COPY_BASE = 0x3f4c3000 19 | DR_REG_EXTMEM_BASE = 0x61800000 20 | DR_REG_MMU_TABLE = 0x61801000 21 | DR_REG_ITAG_TABLE = 0x61802000 22 | DR_REG_DTAG_TABLE = 0x61803000 23 | DR_REG_AES_BASE = 0x6003a000 24 | DR_REG_SHA_BASE = 0x6003b000 25 | DR_REG_RSA_BASE = 0x6003c000 26 | DR_REG_HMAC_BASE = 0x6003e000 27 | DR_REG_DIGITAL_SIGNATURE_BASE = 0x6003d000 28 | DR_REG_CRYPTO_DMA_BASE = 0x6003f000 29 | DR_REG_ASSIST_DEBUG_BASE = 0x3f4ce000 30 | DR_REG_DEDICATED_GPIO_BASE = 0x3f4cf000 31 | DR_REG_INTRUSION_BASE = 0x3f4d0000 32 | DR_REG_UART_BASE = 0x3f400000 33 | DR_REG_SPI1_BASE = 0x3f402000 34 | DR_REG_SPI0_BASE = 0x3f403000 35 | DR_REG_GPIO_BASE = 0x3f404000 36 | DR_REG_GPIO_SD_BASE = 0x3f404f00 37 | DR_REG_FE2_BASE = 0x3f405000 38 | DR_REG_FE_BASE = 0x3f406000 39 | DR_REG_FRC_TIMER_BASE = 0x3f407000 40 | DR_REG_RTCCNTL_BASE = 0x3f408000 41 | DR_REG_RTCIO_BASE = 0x3f408400 42 | DR_REG_SENS_BASE = 0x3f408800 43 | DR_REG_RTC_I2C_BASE = 0x3f408C00 44 | DR_REG_IO_MUX_BASE = 0x3f409000 45 | DR_REG_HINF_BASE = 0x3f40B000 46 | DR_REG_I2S_BASE = 0x3f40F000 47 | DR_REG_UART1_BASE = 0x3f410000 48 | DR_REG_I2C_EXT_BASE = 0x3f413000 49 | DR_REG_UHCI0_BASE = 0x3f414000 50 | DR_REG_SLCHOST_BASE = 0x3f415000 51 | DR_REG_RMT_BASE = 0x3f416000 52 | DR_REG_PCNT_BASE = 0x3f417000 53 | DR_REG_SLC_BASE = 0x3f418000 54 | DR_REG_LEDC_BASE = 0x3f419000 55 | DR_REG_CP_BASE = 0x3f4c3000 56 | DR_REG_EFUSE_BASE = 0x3f41A000 57 | DR_REG_NRX_BASE = 0x3f41CC00 58 | DR_REG_BB_BASE = 0x3f41D000 59 | DR_REG_TIMERGROUP0_BASE = 0x3f41F000 60 | DR_REG_TIMERGROUP1_BASE = 0x3f420000 61 | DR_REG_RTC_SLOWMEM_BASE = 0x3f421000 62 | DR_REG_SYSTIMER_BASE = 0x3f423000 63 | DR_REG_SPI2_BASE = 0x3f424000 64 | DR_REG_SPI3_BASE = 0x3f425000 65 | DR_REG_SYSCON_BASE = 0x3f426000 66 | DR_REG_APB_CTRL_BASE = 0x3f426000 # Old name for SYSCON, to be removed 67 | DR_REG_I2C1_EXT_BASE = 0x3f427000 68 | DR_REG_SPI4_BASE = 0x3f437000 69 | DR_REG_USB_WRAP_BASE = 0x3f439000 70 | DR_REG_APB_SARADC_BASE = 0x3f440000 71 | DR_REG_USB_BASE = 0x60080000 72 | -------------------------------------------------------------------------------- /esp32_ulp/soc_s3.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Address / Register definitions for the ESP32-S3 SoC 10 | """ 11 | 12 | # Reference: 13 | # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s3/include/soc/reg_base.h 14 | 15 | DR_REG_UART_BASE = 0x60000000 16 | DR_REG_SPI1_BASE = 0x60002000 17 | DR_REG_SPI0_BASE = 0x60003000 18 | DR_REG_GPIO_BASE = 0x60004000 19 | DR_REG_GPIO_SD_BASE = 0x60004f00 20 | DR_REG_FE2_BASE = 0x60005000 21 | DR_REG_FE_BASE = 0x60006000 22 | DR_REG_EFUSE_BASE = 0x60007000 23 | DR_REG_RTCCNTL_BASE = 0x60008000 24 | DR_REG_RTCIO_BASE = 0x60008400 25 | DR_REG_SENS_BASE = 0x60008800 26 | DR_REG_RTC_I2C_BASE = 0x60008C00 27 | DR_REG_IO_MUX_BASE = 0x60009000 28 | DR_REG_HINF_BASE = 0x6000B000 29 | DR_REG_UHCI1_BASE = 0x6000C000 30 | DR_REG_I2S_BASE = 0x6000F000 31 | DR_REG_UART1_BASE = 0x60010000 32 | DR_REG_BT_BASE = 0x60011000 33 | DR_REG_I2C_EXT_BASE = 0x60013000 34 | DR_REG_UHCI0_BASE = 0x60014000 35 | DR_REG_SLCHOST_BASE = 0x60015000 36 | DR_REG_RMT_BASE = 0x60016000 37 | DR_REG_PCNT_BASE = 0x60017000 38 | DR_REG_SLC_BASE = 0x60018000 39 | DR_REG_LEDC_BASE = 0x60019000 40 | DR_REG_NRX_BASE = 0x6001CC00 41 | DR_REG_BB_BASE = 0x6001D000 42 | DR_REG_PWM0_BASE = 0x6001E000 43 | DR_REG_TIMERGROUP0_BASE = 0x6001F000 44 | DR_REG_TIMERGROUP1_BASE = 0x60020000 45 | DR_REG_RTC_SLOWMEM_BASE = 0x60021000 46 | DR_REG_SYSTIMER_BASE = 0x60023000 47 | DR_REG_SPI2_BASE = 0x60024000 48 | DR_REG_SPI3_BASE = 0x60025000 49 | DR_REG_SYSCON_BASE = 0x60026000 50 | DR_REG_APB_CTRL_BASE = 0x60026000 # Old name for SYSCON, to be removed 51 | DR_REG_I2C1_EXT_BASE = 0x60027000 52 | DR_REG_SDMMC_BASE = 0x60028000 53 | DR_REG_PERI_BACKUP_BASE = 0x6002A000 54 | DR_REG_TWAI_BASE = 0x6002B000 55 | DR_REG_PWM1_BASE = 0x6002C000 56 | DR_REG_I2S1_BASE = 0x6002D000 57 | DR_REG_UART2_BASE = 0x6002E000 58 | DR_REG_USB_SERIAL_JTAG_BASE = 0x60038000 59 | DR_REG_USB_WRAP_BASE = 0x60039000 60 | DR_REG_AES_BASE = 0x6003A000 61 | DR_REG_SHA_BASE = 0x6003B000 62 | DR_REG_RSA_BASE = 0x6003C000 63 | DR_REG_HMAC_BASE = 0x6003E000 64 | DR_REG_DIGITAL_SIGNATURE_BASE = 0x6003D000 65 | DR_REG_GDMA_BASE = 0x6003F000 66 | DR_REG_APB_SARADC_BASE = 0x60040000 67 | DR_REG_LCD_CAM_BASE = 0x60041000 68 | DR_REG_SYSTEM_BASE = 0x600C0000 69 | DR_REG_SENSITIVE_BASE = 0x600C1000 70 | DR_REG_INTERRUPT_BASE = 0x600C2000 71 | DR_REG_EXTMEM_BASE = 0x600C4000 72 | DR_REG_ASSIST_DEBUG_BASE = 0x600CE000 73 | DR_REG_WCL_BASE = 0x600D0000 74 | -------------------------------------------------------------------------------- /esp32_ulp/util.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | DEBUG = False 9 | 10 | import gc 11 | import os 12 | 13 | NORMAL, WHITESPACE = 0, 1 14 | 15 | 16 | def garbage_collect(msg, verbose=DEBUG): 17 | free_before = gc.mem_free() 18 | gc.collect() 19 | free_after = gc.mem_free() 20 | if verbose: 21 | print("%s: %d --gc--> %d bytes free" % (msg, free_before, free_after)) 22 | 23 | 24 | def split_tokens(line): 25 | buf = "" 26 | tokens = [] 27 | state = NORMAL 28 | for c in line: 29 | if c in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_": 30 | if state != NORMAL: 31 | if len(buf) > 0: 32 | tokens.append(buf) 33 | buf = "" 34 | state = NORMAL 35 | buf += c 36 | elif c in " \t": 37 | if state != WHITESPACE: 38 | if len(buf) > 0: 39 | tokens.append(buf) 40 | buf = "" 41 | state = WHITESPACE 42 | buf += c 43 | else: 44 | if len(buf) > 0: 45 | tokens.append(buf) 46 | buf = "" 47 | tokens.append(c) 48 | 49 | if len(buf) > 0: 50 | tokens.append(buf) 51 | 52 | return tokens 53 | 54 | 55 | def validate_expression(param): 56 | for token in split_tokens(param): 57 | state = 0 58 | for c in token: 59 | if c not in ' \t+-*/%()<>&|~xX0123456789abcdefABCDEF': 60 | return False 61 | 62 | # the following allows hex digits a-f after 0x but not otherwise 63 | if state == 0: 64 | if c in 'abcdefABCDEF': 65 | return False 66 | if c == '0': 67 | state = 1 68 | continue 69 | 70 | if state == 1: 71 | state = 2 if c in 'xX' else 0 72 | continue 73 | 74 | if state == 2: 75 | if c not in '0123456789abcdefABCDEF': 76 | state = 0 77 | return True 78 | 79 | 80 | def file_exists(filename): 81 | try: 82 | os.stat(filename) 83 | return True 84 | except OSError: 85 | pass 86 | return False 87 | -------------------------------------------------------------------------------- /examples/blink.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Example for: ESP32 10 | 11 | Simple example showing how to control a GPIO pin from the ULP coprocessor. 12 | 13 | The GPIO port is configured to be attached to the RTC module, and then set 14 | to OUTPUT mode. To avoid re-initializing the GPIO on every wakeup, a magic 15 | token gets set in memory. 16 | 17 | After every change of state, the ULP is put back to sleep again until the 18 | next wakeup. The ULP wakes up every 500ms to change the state of the GPIO 19 | pin. An LED attached to the GPIO pin would toggle on and off every 500ms. 20 | 21 | The end of the python script has a loop to show the value of the magic token 22 | and the current state, so you can confirm the magic token gets set and watch 23 | the state value changing. If the loop is stopped (Ctrl-C), the LED attached 24 | to the GPIO pin continues to blink, because the ULP runs independently from 25 | the main processor. 26 | """ 27 | 28 | from esp32 import ULP 29 | from machine import mem32 30 | from esp32_ulp import src_to_binary 31 | 32 | source = """\ 33 | # constants from: 34 | # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/reg_base.h 35 | #define DR_REG_RTCIO_BASE 0x3ff48400 36 | 37 | # constants from: 38 | # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_reg.h 39 | #define RTC_IO_TOUCH_PAD2_REG (DR_REG_RTCIO_BASE + 0x9c) 40 | #define RTC_IO_TOUCH_PAD2_MUX_SEL_M (BIT(19)) 41 | #define RTC_GPIO_OUT_REG (DR_REG_RTCIO_BASE + 0x0) 42 | #define RTC_GPIO_ENABLE_REG (DR_REG_RTCIO_BASE + 0xc) 43 | #define RTC_GPIO_ENABLE_S 14 44 | #define RTC_GPIO_OUT_DATA_S 14 45 | 46 | # constants from: 47 | # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_channel.h 48 | #define RTCIO_GPIO2_CHANNEL 12 49 | 50 | # When accessed from the RTC module (ULP) GPIOs need to be addressed by their channel number 51 | .set gpio, RTCIO_GPIO2_CHANNEL 52 | .set token, 0xcafe # magic token 53 | 54 | .text 55 | magic: .long 0 56 | state: .long 0 57 | 58 | .global entry 59 | entry: 60 | # load magic flag 61 | move r0, magic 62 | ld r1, r0, 0 63 | 64 | # test if we have initialised already 65 | sub r1, r1, token 66 | jump after_init, eq # jump if magic == token (note: "eq" means the last instruction (sub) resulted in 0) 67 | 68 | init: 69 | # connect GPIO to ULP (0: GPIO connected to digital GPIO module, 1: GPIO connected to analog RTC module) 70 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_MUX_SEL_M, 1, 1); 71 | 72 | # GPIO shall be output, not input (this also enables a pull-down by default) 73 | WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + gpio, 1, 1) 74 | 75 | # store that we're done with initialisation 76 | move r0, magic 77 | move r1, token 78 | st r1, r0, 0 79 | 80 | after_init: 81 | move r1, state 82 | ld r0, r1, 0 83 | 84 | move r2, 1 85 | sub r0, r2, r0 # toggle state 86 | st r0, r1, 0 # store updated state 87 | 88 | jumpr on, 0, gt # if r0 (state) > 0, jump to 'on' 89 | jump off # else jump to 'off' 90 | 91 | on: 92 | # turn on led (set GPIO) 93 | WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 1) 94 | jump exit 95 | 96 | off: 97 | # turn off led (clear GPIO) 98 | WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 0) 99 | jump exit 100 | 101 | exit: 102 | halt # go back to sleep until next wakeup period 103 | """ 104 | 105 | binary = src_to_binary(source, cpu="esp32") # cpu is esp32 or esp32s2 106 | 107 | load_addr, entry_addr = 0, 8 108 | 109 | ULP_MEM_BASE = 0x50000000 110 | ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits 111 | 112 | ulp = ULP() 113 | ulp.set_wakeup_period(0, 500000) # use timer0, wakeup after 500000usec (0.5s) 114 | ulp.load_binary(load_addr, binary) 115 | 116 | ulp.run(entry_addr) 117 | 118 | while True: 119 | print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK), # magic token 120 | hex(mem32[ULP_MEM_BASE + load_addr + 4] & ULP_DATA_MASK) # current state 121 | ) 122 | -------------------------------------------------------------------------------- /examples/blink_s2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Example for: ESP32-S2 and ESP32-S3 10 | 11 | The GPIO port is configured to be attached to the RTC module, and then set 12 | to OUTPUT mode. To avoid re-initializing the GPIO on every wakeup, a magic 13 | token gets set in memory. 14 | 15 | After every change of state, the ULP is put back to sleep again until the 16 | next wakeup. The ULP wakes up every 500ms to change the state of the GPIO 17 | pin. An LED attached to the GPIO pin would toggle on and off every 500ms. 18 | 19 | The end of the python script has a loop to show the value of the magic token 20 | and the current state, so you can confirm the magic token gets set and watch 21 | the state value changing. If the loop is stopped (Ctrl-C), the LED attached 22 | to the GPIO pin continues to blink, because the ULP runs independently from 23 | the main processor. 24 | """ 25 | 26 | from esp32 import ULP 27 | from machine import mem32 28 | from esp32_ulp import src_to_binary 29 | 30 | source = """\ 31 | # constants from: 32 | # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/reg_base.h 33 | #define DR_REG_RTCIO_BASE 0x3f408400 34 | 35 | # constants from: 36 | # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/rtc_io_reg.h 37 | #define RTC_IO_TOUCH_PAD2_REG (DR_REG_RTCIO_BASE + 0x8c) 38 | #define RTC_IO_TOUCH_PAD2_MUX_SEL_M (BIT(19)) 39 | #define RTC_GPIO_OUT_REG (DR_REG_RTCIO_BASE + 0x0) 40 | #define RTC_GPIO_ENABLE_REG (DR_REG_RTCIO_BASE + 0xc) 41 | #define RTC_GPIO_ENABLE_S 10 42 | #define RTC_GPIO_OUT_DATA_S 10 43 | 44 | # constants from: 45 | # https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/rtc_io_channel.h 46 | #define RTCIO_GPIO2_CHANNEL 2 47 | 48 | # When accessed from the RTC module (ULP) GPIOs need to be addressed by their channel number 49 | .set gpio, RTCIO_GPIO2_CHANNEL 50 | .set token, 0xcafe # magic token 51 | 52 | .text 53 | magic: .long 0 54 | state: .long 0 55 | 56 | .global entry 57 | entry: 58 | # load magic flag 59 | move r0, magic 60 | ld r1, r0, 0 61 | 62 | # test if we have initialised already 63 | sub r1, r1, token 64 | jump after_init, eq # jump if magic == token (note: "eq" means the last instruction (sub) resulted in 0) 65 | 66 | init: 67 | # connect GPIO to ULP (0: GPIO connected to digital GPIO module, 1: GPIO connected to analog RTC module) 68 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_MUX_SEL_M, 1, 1); 69 | 70 | # GPIO shall be output, not input (this also enables a pull-down by default) 71 | WRITE_RTC_REG(RTC_GPIO_ENABLE_REG, RTC_GPIO_ENABLE_S + gpio, 1, 1) 72 | 73 | # store that we're done with initialisation 74 | move r0, magic 75 | move r1, token 76 | st r1, r0, 0 77 | 78 | after_init: 79 | move r1, state 80 | ld r0, r1, 0 81 | 82 | move r2, 1 83 | sub r0, r2, r0 # toggle state 84 | st r0, r1, 0 # store updated state 85 | 86 | jumpr on, 0, gt # if r0 (state) > 0, jump to 'on' 87 | jump off # else jump to 'off' 88 | 89 | on: 90 | # turn on led (set GPIO) 91 | WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 1) 92 | jump exit 93 | 94 | off: 95 | # turn off led (clear GPIO) 96 | WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + gpio, 1, 0) 97 | jump exit 98 | 99 | exit: 100 | halt # go back to sleep until next wakeup period 101 | """ 102 | 103 | binary = src_to_binary(source, cpu="esp32s2") # cpu is esp32 or esp32s2 104 | 105 | load_addr, entry_addr = 0, 8 106 | 107 | ULP_MEM_BASE = 0x50000000 108 | ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits 109 | 110 | ulp = ULP() 111 | ulp.set_wakeup_period(0, 500000) # use timer0, wakeup after 500000usec (0.5s) 112 | ulp.load_binary(load_addr, binary) 113 | 114 | ulp.run(entry_addr) 115 | 116 | while True: 117 | print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK), # magic token 118 | hex(mem32[ULP_MEM_BASE + load_addr + 4] & ULP_DATA_MASK) # current state 119 | ) 120 | -------------------------------------------------------------------------------- /examples/counter.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Example for: ESP32 10 | 11 | Very basic example showing data exchange main CPU <--> ULP coprocessor. 12 | 13 | To show that the ULP is doing something, it just increments the value . 14 | It does that once per ulp timer wakeup (and then the ULP halts until it gets 15 | waked up via the timer again). 16 | 17 | The timer is set to a rather long period, so you can watch the data value 18 | incrementing (see loop at the end). 19 | """ 20 | 21 | from esp32 import ULP 22 | from machine import mem32 23 | 24 | from esp32_ulp import src_to_binary 25 | 26 | source = """\ 27 | data: .long 0 28 | 29 | entry: move r3, data # load address of data into r3 30 | ld r2, r3, 0 # load data contents ([r3+0]) into r2 31 | add r2, r2, 1 # increment r2 32 | st r2, r3, 0 # store r2 contents into data ([r3+0]) 33 | 34 | halt # halt ULP co-prozessor (until it gets waked up again) 35 | """ 36 | 37 | binary = src_to_binary(source, cpu="esp32") # cpu is esp32 or esp32s2 38 | 39 | load_addr, entry_addr = 0, 4 40 | 41 | ULP_MEM_BASE = 0x50000000 42 | ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits 43 | 44 | ulp = ULP() 45 | ulp.set_wakeup_period(0, 50000) # use timer0, wakeup after 50.000 cycles 46 | ulp.load_binary(load_addr, binary) 47 | 48 | mem32[ULP_MEM_BASE + load_addr] = 0x1000 49 | ulp.run(entry_addr) 50 | 51 | while True: 52 | print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK)) 53 | 54 | -------------------------------------------------------------------------------- /examples/counter_s2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Example for: ESP32-S2 and ESP32-S3 10 | 11 | Very basic example showing data exchange main CPU <--> ULP coprocessor. 12 | 13 | To show that the ULP is doing something, it just increments the value . 14 | It does that once per ulp timer wakeup (and then the ULP halts until it gets 15 | waked up via the timer again). 16 | 17 | The timer is set to a rather long period, so you can watch the data value 18 | incrementing (see loop at the end). 19 | """ 20 | 21 | from esp32 import ULP 22 | from machine import mem32 23 | 24 | from esp32_ulp import src_to_binary 25 | 26 | source = """\ 27 | data: .long 0 28 | 29 | entry: move r3, data # load address of data into r3 30 | ld r2, r3, 0 # load data contents ([r3+0]) into r2 31 | add r2, r2, 1 # increment r2 32 | st r2, r3, 0 # store r2 contents into data ([r3+0]) 33 | 34 | halt # halt ULP co-prozessor (until it gets waked up again) 35 | """ 36 | 37 | binary = src_to_binary(source, cpu="esp32s2") # cpu is esp32 or esp32s2 38 | 39 | load_addr, entry_addr = 0, 4 40 | 41 | ULP_MEM_BASE = 0x50000000 42 | ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits 43 | 44 | ulp = ULP() 45 | ulp.set_wakeup_period(0, 50000) # use timer0, wakeup after 50.000 cycles 46 | ulp.load_binary(load_addr, binary) 47 | 48 | mem32[ULP_MEM_BASE + load_addr] = 0x1000 49 | ulp.run(entry_addr) 50 | 51 | while True: 52 | print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK)) 53 | 54 | -------------------------------------------------------------------------------- /examples/readgpio.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Example for: ESP32 10 | 11 | Very basic example showing how to read a GPIO pin from the ULP and access 12 | that data from the main CPU. 13 | 14 | In this case GPIO4 is being read. Note that the ULP needs to refer to GPIOs 15 | via their RTC channel number. You can see the mapping in this file: 16 | https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/include/soc/rtc_io_channel.h#L51 17 | 18 | If you change to a different GPIO number, make sure to modify both the channel 19 | number and also the RTC_IO_TOUCH_PAD0_* references appropriately. The best place 20 | to see the mappings might be this table here (notice the "real GPIO numbers" as 21 | comments to each line): 22 | https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32/rtc_io_periph.c#L53 23 | 24 | The timer is set to a rather long period, so you can watch the data value 25 | change as you change the GPIO input (see loop at the end). 26 | """ 27 | 28 | from esp32 import ULP 29 | from machine import mem32 30 | 31 | from esp32_ulp import src_to_binary 32 | 33 | source = """\ 34 | #define DR_REG_RTCIO_BASE 0x3ff48400 35 | #define RTC_IO_TOUCH_PAD0_REG (DR_REG_RTCIO_BASE + 0x94) 36 | #define RTC_IO_TOUCH_PAD0_MUX_SEL_M (BIT(19)) 37 | #define RTC_IO_TOUCH_PAD0_FUN_IE_M (BIT(13)) 38 | #define RTC_GPIO_IN_REG (DR_REG_RTCIO_BASE + 0x24) 39 | #define RTC_GPIO_IN_NEXT_S 14 40 | .set channel, 10 # 10 is the channel no. of gpio4 41 | 42 | state: .long 0 43 | 44 | entry: 45 | # connect GPIO to the RTC subsystem so the ULP can read it 46 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD0_REG, RTC_IO_TOUCH_PAD0_MUX_SEL_M, 1, 1) 47 | 48 | # switch the GPIO into input mode 49 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD0_REG, RTC_IO_TOUCH_PAD0_FUN_IE_M, 1, 1) 50 | 51 | # read the GPIO's current state into r0 52 | READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + channel, 1) 53 | 54 | # set r3 to the memory address of "state" 55 | move r3, state 56 | 57 | # store what was read into r0 into the "state" variable 58 | st r0, r3, 0 59 | 60 | # halt ULP co-processor (until it gets woken up again) 61 | halt 62 | """ 63 | 64 | binary = src_to_binary(source, cpu="esp32") # cpu is esp32 or esp32s2 65 | 66 | load_addr, entry_addr = 0, 4 67 | 68 | ULP_MEM_BASE = 0x50000000 69 | ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits 70 | 71 | ulp = ULP() 72 | ulp.set_wakeup_period(0, 50000) # use timer0, wakeup after 50.000 cycles 73 | ulp.load_binary(load_addr, binary) 74 | 75 | mem32[ULP_MEM_BASE + load_addr] = 0x0 # initialise state to 0 76 | ulp.run(entry_addr) 77 | 78 | while True: 79 | print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK)) 80 | 81 | -------------------------------------------------------------------------------- /examples/readgpio_s2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Example for: ESP32-S2 10 | 11 | Very basic example showing how to read a GPIO pin from the ULP and access 12 | that data from the main CPU. 13 | 14 | In this case GPIO4 is being read. Note that the ULP needs to refer to GPIOs 15 | via their RTC channel number. You can see the mapping in this file: 16 | https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/include/soc/rtc_io_channel.h#L33 17 | 18 | If you change to a different GPIO number, make sure to modify both the channel 19 | number and also the RTC_IO_TOUCH_PAD4_* references appropriately. The best place 20 | to see the mappings might be this table here (notice the "real GPIO numbers" as 21 | comments to each line): 22 | https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s2/rtc_io_periph.c#L60 23 | 24 | The timer is set to a rather long period, so you can watch the data value 25 | change as you change the GPIO input (see loop at the end). 26 | """ 27 | 28 | from esp32 import ULP 29 | from machine import mem32 30 | 31 | from esp32_ulp import src_to_binary 32 | 33 | source = """\ 34 | #define DR_REG_RTCIO_BASE 0x3f408400 35 | #define RTC_IO_TOUCH_PAD4_REG (DR_REG_RTCIO_BASE + 0x94) 36 | #define RTC_IO_TOUCH_PAD4_MUX_SEL_M (BIT(19)) 37 | #define RTC_IO_TOUCH_PAD4_FUN_IE_M (BIT(13)) 38 | #define RTC_GPIO_IN_REG (DR_REG_RTCIO_BASE + 0x24) 39 | #define RTC_GPIO_IN_NEXT_S 10 40 | #define DR_REG_SENS_BASE 0x3f408800 41 | #define SENS_SAR_IO_MUX_CONF_REG (DR_REG_SENS_BASE + 0x0144) 42 | #define SENS_IOMUX_CLK_GATE_EN (BIT(31)) 43 | .set channel, 4 44 | 45 | state: .long 0 46 | 47 | entry: 48 | # enable IOMUX clock 49 | WRITE_RTC_FIELD(SENS_SAR_IO_MUX_CONF_REG, SENS_IOMUX_CLK_GATE_EN, 1) 50 | 51 | # connect GPIO to the RTC subsystem so the ULP can read it 52 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD4_REG, RTC_IO_TOUCH_PAD4_MUX_SEL_M, 1, 1) 53 | 54 | # switch the GPIO into input mode 55 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD4_REG, RTC_IO_TOUCH_PAD4_FUN_IE_M, 1, 1) 56 | 57 | # read the GPIO's current state into r0 58 | READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + channel, 1) 59 | 60 | # set r3 to the memory address of "state" 61 | move r3, state 62 | 63 | # store what was read into r0 into the "state" variable 64 | st r0, r3, 0 65 | 66 | # halt ULP co-processor (until it gets woken up again) 67 | halt 68 | """ 69 | 70 | binary = src_to_binary(source, cpu="esp32s2") # cpu is esp32 or esp32s2 71 | 72 | load_addr, entry_addr = 0, 4 73 | 74 | ULP_MEM_BASE = 0x50000000 75 | ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits 76 | 77 | ulp = ULP() 78 | ulp.set_wakeup_period(0, 50000) # use timer0, wakeup after 50.000 cycles 79 | ulp.load_binary(load_addr, binary) 80 | 81 | mem32[ULP_MEM_BASE + load_addr] = 0x0 # initialise state to 0 82 | ulp.run(entry_addr) 83 | 84 | while True: 85 | print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK)) 86 | 87 | -------------------------------------------------------------------------------- /examples/readgpio_s3.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Example for: ESP32-S3 10 | 11 | Very basic example showing how to read a GPIO pin from the ULP and access 12 | that data from the main CPU. 13 | 14 | In this case GPIO4 is being read. Note that the ULP needs to refer to GPIOs 15 | via their RTC channel number. You can see the mapping in this file: 16 | https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s3/include/soc/rtc_io_channel.h#L33 17 | 18 | If you change to a different GPIO number, make sure to modify both the channel 19 | number and also the RTC_IO_TOUCH_PAD2_* references appropriately. The best place 20 | to see the mappings might be this table here (notice the "real GPIO numbers" as 21 | comments to each line): 22 | https://github.com/espressif/esp-idf/blob/v5.0.2/components/soc/esp32s3/rtc_io_periph.c#L60 23 | 24 | The timer is set to a rather long period, so you can watch the data value 25 | change as you change the GPIO input (see loop at the end). 26 | """ 27 | 28 | from esp32 import ULP 29 | from machine import mem32 30 | 31 | from esp32_ulp import src_to_binary 32 | 33 | source = """\ 34 | #define DR_REG_RTCIO_BASE 0x60008400 35 | #define RTC_IO_TOUCH_PAD2_REG (DR_REG_RTCIO_BASE + 0x8c) 36 | #define RTC_IO_TOUCH_PAD2_MUX_SEL_M (BIT(19)) 37 | #define RTC_IO_TOUCH_PAD2_FUN_IE_M (BIT(13)) 38 | #define RTC_GPIO_IN_REG (DR_REG_RTCIO_BASE + 0x24) 39 | #define RTC_GPIO_IN_NEXT_S 10 40 | #define DR_REG_SENS_BASE 0x60008800 41 | #define SENS_SAR_PERI_CLK_GATE_CONF_REG (DR_REG_SENS_BASE + 0x104) 42 | #define SENS_IOMUX_CLK_EN (BIT(31)) 43 | .set channel, 2 44 | 45 | state: .long 0 46 | 47 | entry: 48 | # enable IOMUX clock 49 | WRITE_RTC_FIELD(SENS_SAR_PERI_CLK_GATE_CONF_REG, SENS_IOMUX_CLK_EN, 1) 50 | 51 | # connect GPIO to the RTC subsystem so the ULP can read it 52 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_MUX_SEL_M, 1, 1) 53 | 54 | # switch the GPIO into input mode 55 | WRITE_RTC_REG(RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_FUN_IE_M, 1, 1) 56 | 57 | # read the GPIO's current state into r0 58 | READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + channel, 1) 59 | 60 | # set r3 to the memory address of "state" 61 | move r3, state 62 | 63 | # store what was read into r0 into the "state" variable 64 | st r0, r3, 0 65 | 66 | # halt ULP co-processor (until it gets woken up again) 67 | halt 68 | """ 69 | 70 | binary = src_to_binary(source, cpu="esp32s2") # cpu is esp32 or esp32s2 71 | 72 | load_addr, entry_addr = 0, 4 73 | 74 | ULP_MEM_BASE = 0x50000000 75 | ULP_DATA_MASK = 0xffff # ULP data is only in lower 16 bits 76 | 77 | ulp = ULP() 78 | ulp.set_wakeup_period(0, 50000) # use timer0, wakeup after 50.000 cycles 79 | ulp.load_binary(load_addr, binary) 80 | 81 | mem32[ULP_MEM_BASE + load_addr] = 0x0 # initialise state to 0 82 | ulp.run(entry_addr) 83 | 84 | while True: 85 | print(hex(mem32[ULP_MEM_BASE + load_addr] & ULP_DATA_MASK)) 86 | 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "v":1, 3 | "urls":[ 4 | ["esp32_ulp/__init__.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/__init__.py"], 5 | ["esp32_ulp/__main__.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/__main__.py"], 6 | ["esp32_ulp/assemble.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/assemble.py"], 7 | ["esp32_ulp/definesdb.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/definesdb.py"], 8 | ["esp32_ulp/link.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/link.py"], 9 | ["esp32_ulp/nocomment.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/nocomment.py"], 10 | ["esp32_ulp/opcodes.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/opcodes.py"], 11 | ["esp32_ulp/opcodes_s2.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/opcodes_s2.py"], 12 | ["esp32_ulp/parse_to_db.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/parse_to_db.py"], 13 | ["esp32_ulp/preprocess.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/preprocess.py"], 14 | ["esp32_ulp/soc.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/soc.py"], 15 | ["esp32_ulp/soc_s2.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/soc_s2.py"], 16 | ["esp32_ulp/soc_s3.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/soc_s3.py"], 17 | ["esp32_ulp/util.py", "github:micropython/micropython-esp32-ulp/esp32_ulp/util.py"] 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /sdist_upip.py: -------------------------------------------------------------------------------- 1 | # This module is part of Pycopy https://github.com/pfalcon/pycopy 2 | # and pycopy-lib https://github.com/pfalcon/pycopy-lib, projects to 3 | # create a (very) lightweight full-stack Python distribution. 4 | # 5 | # Copyright (c) 2016-2019 Paul Sokolovsky 6 | # Licence: MIT 7 | # 8 | # This module overrides distutils (also compatible with setuptools) "sdist" 9 | # command to perform pre- and post-processing as required for Pycopy's 10 | # upip package manager. 11 | # 12 | # Preprocessing steps: 13 | # * Creation of Python resource module (R.py) from each top-level package's 14 | # resources. 15 | # Postprocessing steps: 16 | # * Removing metadata files not used by upip (this includes setup.py) 17 | # * Recompressing gzip archive with 4K dictionary size so it can be 18 | # installed even on low-heap targets. 19 | # 20 | import sys 21 | import os 22 | import zlib 23 | from subprocess import Popen, PIPE 24 | import glob 25 | import tarfile 26 | import re 27 | import io 28 | 29 | from distutils.filelist import FileList 30 | from setuptools.command.sdist import sdist as _sdist 31 | 32 | 33 | def gzip_4k(inf, fname): 34 | comp = zlib.compressobj(level=9, wbits=16 + 12) 35 | with open(fname + ".out", "wb") as outf: 36 | while 1: 37 | data = inf.read(1024) 38 | if not data: 39 | break 40 | outf.write(comp.compress(data)) 41 | outf.write(comp.flush()) 42 | os.rename(fname, fname + ".orig") 43 | os.rename(fname + ".out", fname) 44 | 45 | 46 | FILTERS = [ 47 | # include, exclude, repeat 48 | (r".+\.egg-info/(PKG-INFO|requires\.txt)", r"setup.py$"), 49 | (r".+\.py$", r"[^/]+$"), 50 | (None, r".+\.egg-info/.+"), 51 | ] 52 | 53 | 54 | outbuf = io.BytesIO() 55 | 56 | def filter_tar(name): 57 | fin = tarfile.open(name, "r:gz") 58 | fout = tarfile.open(fileobj=outbuf, mode="w") 59 | for info in fin: 60 | # print(info) 61 | if not "/" in info.name: 62 | continue 63 | fname = info.name.split("/", 1)[1] 64 | include = None 65 | 66 | for inc_re, exc_re in FILTERS: 67 | if include is None and inc_re: 68 | if re.match(inc_re, fname): 69 | include = True 70 | 71 | if include is None and exc_re: 72 | if re.match(exc_re, fname): 73 | include = False 74 | 75 | if include is None: 76 | include = True 77 | 78 | if include: 79 | print("including:", fname) 80 | else: 81 | print("excluding:", fname) 82 | continue 83 | 84 | farch = fin.extractfile(info) 85 | fout.addfile(info, farch) 86 | fout.close() 87 | fin.close() 88 | 89 | 90 | def make_resource_module(manifest_files): 91 | resources = [] 92 | # Any non-python file included in manifest is resource 93 | for fname in manifest_files: 94 | ext = fname.rsplit(".", 1) 95 | if len(ext) > 1: 96 | ext = ext[1] 97 | else: 98 | ext = "" 99 | if ext != "py": 100 | resources.append(fname) 101 | 102 | if resources: 103 | print("creating resource module R.py") 104 | resources.sort() 105 | last_pkg = None 106 | r_file = None 107 | for fname in resources: 108 | try: 109 | pkg, res_name = fname.split("/", 1) 110 | except ValueError: 111 | print("not treating %s as a resource" % fname) 112 | continue 113 | if last_pkg != pkg: 114 | last_pkg = pkg 115 | if r_file: 116 | r_file.write("}\n") 117 | r_file.close() 118 | r_file = open(pkg + "/R.py", "w") 119 | r_file.write("R = {\n") 120 | 121 | with open(fname, "rb") as f: 122 | r_file.write("%r: %r,\n" % (res_name, f.read())) 123 | 124 | if r_file: 125 | r_file.write("}\n") 126 | r_file.close() 127 | 128 | 129 | class sdist(_sdist): 130 | 131 | def run(self): 132 | self.filelist = FileList() 133 | self.get_file_list() 134 | make_resource_module(self.filelist.files) 135 | 136 | r = super().run() 137 | 138 | assert len(self.archive_files) == 1 139 | print("filtering files and recompressing with 4K dictionary") 140 | filter_tar(self.archive_files[0]) 141 | outbuf.seek(0) 142 | gzip_4k(outbuf, self.archive_files[0]) 143 | 144 | return r 145 | 146 | 147 | # For testing only 148 | if __name__ == "__main__": 149 | filter_tar(sys.argv[1]) 150 | outbuf.seek(0) 151 | gzip_4k(outbuf, sys.argv[1]) 152 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | import re 9 | from setuptools import setup 10 | import sdist_upip 11 | 12 | 13 | def long_desc_from_readme(): 14 | with open('README.rst', 'r') as fd: 15 | long_description = fd.read() 16 | 17 | # remove badges 18 | long_description = re.compile(r'^\.\. start-badges.*^\.\. end-badges', re.M | re.S).sub('', long_description) 19 | 20 | # strip links. keep link name and use literal text formatting 21 | long_description = re.sub(r'`([^<`]+) ]+>`_', '``\\1``', long_description) 22 | 23 | return long_description 24 | 25 | 26 | setup( 27 | name="micropython-esp32-ulp", 28 | use_scm_version={ 29 | 'local_scheme': 'no-local-version', 30 | }, 31 | description="Assembler toolchain for the ESP32 ULP co-processor, written in MicroPython", 32 | long_description=long_desc_from_readme(), 33 | long_description_content_type='text/x-rst', 34 | url="https://github.com/micropython/micropython-esp32-ulp", 35 | license="MIT", 36 | author="micropython-esp32-ulp authors", 37 | author_email="tw@waldmann-edv.de", 38 | maintainer="micropython-esp32-ulp authors", 39 | maintainer_email="tw@waldmann-edv.de", 40 | classifiers=[ 41 | 'License :: OSI Approved :: MIT License', 42 | 'Programming Language :: Python :: Implementation :: MicroPython', 43 | ], 44 | setup_requires=['setuptools_scm'], 45 | platforms=["esp32", "linux", "darwin"], 46 | cmdclass={"sdist": sdist_upip.sdist}, 47 | packages=["esp32_ulp"], 48 | ) 49 | -------------------------------------------------------------------------------- /tests/00_unit_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of the micropython-esp32-ulp project, 4 | # https://github.com/micropython/micropython-esp32-ulp 5 | # 6 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 7 | # SPDX-License-Identifier: MIT 8 | 9 | # export PYTHONPATH=.:$PYTHONPATH 10 | 11 | set -e 12 | 13 | LIST=${1:-opcodes opcodes_s2 assemble link util preprocess definesdb decode decode_s2} 14 | 15 | for file in $LIST; do 16 | echo testing $file... 17 | micropython $file.py 18 | done 19 | -------------------------------------------------------------------------------- /tests/01_compat_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of the micropython-esp32-ulp project, 4 | # https://github.com/micropython/micropython-esp32-ulp 5 | # 6 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 7 | # SPDX-License-Identifier: MIT 8 | 9 | # export PYTHONPATH=.:$PYTHONPATH 10 | 11 | set -e 12 | 13 | calc_file_hash() { 14 | local filename=$1 15 | 16 | shasum < $1 | cut -d' ' -f1 17 | } 18 | 19 | make_log_dir() { 20 | mkdir -p log 21 | } 22 | 23 | fetch_esp_idf() { 24 | [ -d esp-idf ] && return 25 | 26 | echo "Fetching esp-idf" 27 | log_file=log/fetch-esp-idf.log 28 | git clone --depth 1 \ 29 | https://github.com/espressif/esp-idf.git 1>$log_file 2>&1 30 | } 31 | 32 | run_tests_for_cpu() { 33 | local cpu=$1 34 | echo "Testing for CPU: $cpu" 35 | 36 | for src_file in $(ls -1 compat/*.S fixtures/*.S); do 37 | src_name="${src_file%.S}" 38 | 39 | # files with a cpu encoded into their name are only run for that cpu 40 | if [[ $src_file =~ \.esp32\. && $cpu != esp32 ]] || [[ $src_file =~ \.esp32s2?\. && $cpu != esp32s2 ]]; then 41 | continue 42 | fi 43 | echo "Testing $src_file" 44 | echo -e "\tBuilding using micropython-esp32-ulp ($cpu)" 45 | ulp_file="${src_name}.ulp" 46 | log_file="${src_name}.log" 47 | micropython -m esp32_ulp -c $cpu $src_file 1>$log_file # generates $ulp_file 48 | 49 | pre_file="${src_name}.pre" 50 | obj_file="${src_name}.o" 51 | elf_file="${src_name}.elf" 52 | bin_file="${src_name}.bin" 53 | 54 | echo -e "\tBuilding using binutils ($cpu)" 55 | gcc -I esp-idf/components/soc/$cpu/include -I esp-idf/components/esp_common/include \ 56 | -I esp-idf/components/soc/$cpu/register \ 57 | -x assembler-with-cpp \ 58 | -E -o ${pre_file} $src_file 59 | esp32ulp-elf-as --mcpu=$cpu -o $obj_file ${pre_file} 60 | esp32ulp-elf-ld -T esp32.ulp.ld -o $elf_file $obj_file 61 | esp32ulp-elf-objcopy -O binary $elf_file $bin_file 62 | 63 | if ! diff $ulp_file $bin_file 1>/dev/null; then 64 | echo -e "\tBuild outputs differ!" 65 | echo "" 66 | echo "Compatibility test failed for $src_file" 67 | echo "micropython-esp32-ulp log:" 68 | cat $log_file 69 | echo "micropython-esp32-ulp output:" 70 | xxd $ulp_file 71 | echo "binutils output:" 72 | xxd $bin_file 73 | exit 1 74 | else 75 | echo -e "\tBuild outputs match (sha1: $(calc_file_hash $ulp_file))" 76 | fi 77 | done 78 | echo "" 79 | } 80 | 81 | make_log_dir 82 | fetch_esp_idf 83 | run_tests_for_cpu esp32 84 | run_tests_for_cpu esp32s2 85 | -------------------------------------------------------------------------------- /tests/02_compat_rtc_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of the micropython-esp32-ulp project, 4 | # https://github.com/micropython/micropython-esp32-ulp 5 | # 6 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 7 | # SPDX-License-Identifier: MIT 8 | 9 | # export PYTHONPATH=.:$PYTHONPATH 10 | 11 | set -e 12 | 13 | make_log_dir() { 14 | mkdir -p log 15 | } 16 | 17 | fetch_esp_idf() { 18 | [ -d esp-idf ] && return 19 | 20 | echo "Fetching esp-idf" 21 | log_file=log/fetch-esp-idf.log 22 | git clone --depth 1 \ 23 | https://github.com/espressif/esp-idf.git 1>$log_file 2>&1 24 | } 25 | 26 | fetch_ulptool_examples() { 27 | [ -d ulptool ] && return 28 | 29 | echo "Fetching ulptool examples" 30 | log_file=log/fetch-ulptool.log 31 | git clone --depth 1 \ 32 | https://github.com/duff2013/ulptool 1>$log_file 2>&1 33 | } 34 | 35 | fetch_binutils_esp32ulp_examples() { 36 | [ -d binutils-gdb ] && return 37 | 38 | echo "Fetching binutils-gdb (esp32ulp) examples" 39 | log_file=log/fetch-binutils.log 40 | git clone --depth 1 \ 41 | -b esp32ulp-elf-v2.35_20220830 \ 42 | https://github.com/espressif/binutils-gdb.git 1>$log_file 2>&1 43 | } 44 | 45 | REUSE_DEFINES_DB=0 46 | 47 | build_defines_db() { 48 | local cpu=$1 49 | local defines_db=defines.db 50 | local defines_db_cpu=defines.$cpu.db 51 | 52 | if [ "$REUSE_DEFINES_DB" = 1 ] && [ -s "${defines_db_cpu}" ]; then 53 | # reuse existing defines.db 54 | echo "Reusing existing defines DB for cpu $cpu" 55 | cp ${defines_db_cpu} ${defines_db} 56 | return 57 | fi 58 | 59 | echo "Building defines DB from $cpu include files" 60 | log_file=log/build_defines_db.$cpu.log 61 | rm -f "${defines_db}" 62 | micropython -m esp32_ulp.parse_to_db \ 63 | esp-idf/components/soc/$cpu/include/soc/*.h \ 64 | esp-idf/components/soc/$cpu/register/soc/*.h \ 65 | esp-idf/components/esp_common/include/*.h 1>$log_file 66 | 67 | # cache defines.db 68 | cp ${defines_db} ${defines_db_cpu} 69 | } 70 | 71 | calc_file_hash() { 72 | local filename=$1 73 | 74 | shasum < $1 | cut -d' ' -f1 75 | } 76 | 77 | patch_test() { 78 | local test_name=$1 79 | local out_file="${test_name}.tmp" 80 | 81 | if [[ "${test_name}" =~ ^(esp32ulp_jumpr|esp32s2ulp_jumpr|esp32s2ulp_jump)$ ]]; then 82 | ( 83 | cd binutils-gdb/gas/testsuite/gas/esp32ulp/$cpu 84 | cp ${test_name}.s ${out_file} 85 | echo -e "\tPatching test to work around binutils-esp32ulp .global bug" 86 | cat >> ${out_file} < ${out_file} 98 | echo -e "\tPatching test to work around binutils-gdb (esp32ulp) .global bug" 99 | cat >> ${out_file} <> ${out_file} 114 | ) 115 | return 0 116 | fi 117 | 118 | return 1 # nothing was patched 119 | } 120 | 121 | make_log_dir 122 | fetch_esp_idf 123 | fetch_ulptool_examples 124 | fetch_binutils_esp32ulp_examples 125 | 126 | run_tests_for_cpu() { 127 | local cpu=$1 128 | echo "Testing for CPU: $cpu" 129 | build_defines_db $cpu 130 | 131 | LIST=$(echo binutils-gdb/gas/testsuite/gas/esp32ulp/$cpu/*.s) 132 | if [ $cpu = esp32 ]; then 133 | # append extra tests to test preprocessor 134 | # examples have constants specific to ESP32 (original) 135 | # so we only run these tests with cpu = esp32 136 | # these tests primarily test our preprocessor, which is 137 | # cpu independent, so we do not need to run them 138 | # per each cpu. 139 | LIST=$(echo ulptool/src/ulp_examples/*/*.s $LIST) 140 | fi 141 | 142 | for src_file in $LIST; do 143 | 144 | src_name="${src_file%.s}" 145 | src_dir="${src_name%/*}" 146 | 147 | echo "Testing $src_file" 148 | 149 | test_name="${src_name##*/}" 150 | 151 | # for now, skip files that contain unsupported things (macros) 152 | for I in i2c i2c_dev stack i2c_wr test1 test_jumpr test_macro; do 153 | if [ "${test_name}" = "$I" ]; then 154 | echo -e "\tSkipping... not yet supported" 155 | continue 2 156 | fi 157 | done 158 | 159 | if [ "$cpu" = esp32s2 ]; then 160 | if [ "${test_name}" = "hall_sensor" ]; then 161 | echo -e "\tSkipping... not supported on $cpu" 162 | continue 1 163 | fi 164 | fi 165 | 166 | # BEGIN: work around known issues with binutils-gdb (esp32ulp) 167 | ulp_file="${src_name}.ulp" 168 | 169 | if patch_test ${test_name}; then 170 | # switch to the patched file instead of original one 171 | src_file="${src_dir}/${test_name}.tmp" 172 | src_name="${src_file%.tmp}" 173 | ulp_file="${src_name}.tmp.ulp" # when extension is not .s, micropython-esp32-ulp doesn't remove original extension 174 | fi 175 | # END: work around known issues with binutils-gdb (esp32ulp) 176 | 177 | echo -e "\tBuilding using micropython-esp32-ulp ($cpu)" 178 | log_file="${src_name}.log" 179 | micropython -m esp32_ulp -c $cpu $src_file 1>$log_file # generates $ulp_file 180 | 181 | pre_file="${src_name}.pre" 182 | obj_file="${src_name}.o" 183 | elf_file="${src_name}.elf" 184 | bin_file="${src_name}.bin" 185 | 186 | echo -e "\tBuilding using binutils ($cpu)" 187 | gcc -I esp-idf/components/soc/$cpu/include -I esp-idf/components/esp_common/include \ 188 | -I esp-idf/components/soc/$cpu/register \ 189 | -x assembler-with-cpp \ 190 | -E -o ${pre_file} $src_file 191 | esp32ulp-elf-as --mcpu=$cpu -o $obj_file ${pre_file} 192 | esp32ulp-elf-ld -T esp32.ulp.ld -o $elf_file $obj_file 193 | esp32ulp-elf-objcopy -O binary $elf_file $bin_file 194 | 195 | if ! diff $ulp_file $bin_file 1>/dev/null; then 196 | echo -e "\tBuild outputs differ!" 197 | echo "" 198 | echo "Compatibility test failed for $src_file" 199 | echo "micropython-esp32-ulp log:" 200 | cat $log_file 201 | echo "micropython-esp32-ulp output:" 202 | xxd $ulp_file 203 | echo "binutils output:" 204 | xxd $bin_file 205 | exit 1 206 | else 207 | echo -e "\tBuild outputs match (sha1: $(calc_file_hash $ulp_file))" 208 | fi 209 | done 210 | echo "" 211 | } 212 | 213 | if [ "$1" = -r ]; then 214 | REUSE_DEFINES_DB=1 215 | fi 216 | 217 | run_tests_for_cpu esp32 218 | run_tests_for_cpu esp32s2 219 | -------------------------------------------------------------------------------- /tests/03_disassembler_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is part of the micropython-esp32-ulp project, 4 | # https://github.com/micropython/micropython-esp32-ulp 5 | # 6 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 7 | # SPDX-License-Identifier: MIT 8 | 9 | set -e 10 | 11 | test_disassembling_a_file() { 12 | local cpu=$1 13 | local verbose 14 | if [ "$2" == verbose ]; then 15 | verbose=-v 16 | echo -e "Testing disassembling a file in VERBOSE mode" 17 | else 18 | echo -e "Testing disassembling a file in NORMAL mode" 19 | fi 20 | 21 | testname=all_opcodes 22 | fixture=fixtures/${testname}.${cpu}.S 23 | echo -e "\tBuilding $fixture using micropython-esp32-ulp ($cpu)" 24 | 25 | log_file="${testname}.log" 26 | ulp_file="fixtures/${testname}.${cpu}.ulp" 27 | micropython -m esp32_ulp -c $cpu $fixture 1>$log_file # generates $ulp_file 28 | 29 | lst_file="${testname}.$cpu.lst" 30 | lst_file_fixture=fixtures/${testname}${verbose}.$cpu.lst 31 | echo -e "\tDisassembling $ulp_file using micropython-esp32-ulp disassembler ($cpu)" 32 | micropython -m tools.disassemble -c $cpu $verbose $ulp_file > $lst_file 33 | 34 | if ! diff $lst_file_fixture $lst_file 1>/dev/null; then 35 | echo -e "\tDisassembled output differs from expected output!" 36 | echo "" 37 | echo "Disassembly test failed for $fixture" 38 | echo "micropython-esp32-ulp log:" 39 | cat $log_file 40 | echo "Diff of disassembly: expected vs actual" 41 | diff -u $lst_file_fixture $lst_file 42 | fi 43 | } 44 | 45 | test_disassembling_a_manual_sequence() { 46 | local cpu=$1 47 | local verbose 48 | if [ "$2" == verbose ]; then 49 | verbose=-v 50 | echo -e "Testing disassembling a manual byte sequence in VERBOSE mode" 51 | else 52 | echo -e "Testing disassembling a manual byte sequence in NORMAL mode" 53 | fi 54 | 55 | if [ "$cpu" == "esp32s2" ]; then 56 | sequence="e1af 8c74 8101 0068 2705 cc19 0005 681d 0000 00a0 0000 0078" 57 | else 58 | sequence="e1af 8c72 0100 0068 2705 cc19 0005 681d 0000 00a0 0000 0074" 59 | fi 60 | 61 | lst_file="manual_bytes.$cpu.lst" 62 | lst_file_fixture=fixtures/manual_bytes${verbose}.$cpu.lst 63 | echo -e "\tDisassembling manual byte sequence using micropython-esp32-ulp disassembler ($cpu)" 64 | micropython -m tools.disassemble -c $cpu $verbose -m $sequence> $lst_file 65 | 66 | if ! diff $lst_file_fixture $lst_file 1>/dev/null; then 67 | echo -e "\tDisassembled output differs from expected output!" 68 | echo "" 69 | echo "Disassembly test failed for manual byte sequence" 70 | echo "Diff of disassembly: expected vs actual" 71 | diff -u $lst_file_fixture $lst_file 72 | fi 73 | } 74 | 75 | # esp32 76 | echo "Testing for CPU: esp32" 77 | test_disassembling_a_file esp32 78 | test_disassembling_a_file esp32 verbose 79 | 80 | test_disassembling_a_manual_sequence esp32 81 | test_disassembling_a_manual_sequence esp32 verbose 82 | 83 | echo "" 84 | 85 | # esp32s2 86 | echo "Testing for CPU: esp32s2" 87 | test_disassembling_a_file esp32s2 88 | test_disassembling_a_file esp32s2 verbose 89 | 90 | test_disassembling_a_manual_sequence esp32s2 91 | test_disassembling_a_manual_sequence esp32s2 verbose 92 | -------------------------------------------------------------------------------- /tests/assemble.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from esp32_ulp.assemble import Assembler, TEXT, DATA, BSS, REL, ABS 9 | from esp32_ulp.assemble import SymbolTable 10 | from esp32_ulp.nocomment import remove_comments 11 | 12 | src = """\ 13 | .set const, 123 14 | .set const_left, 976 15 | 16 | start: wait 42 17 | ld r0, r1, 0 18 | st r0, r1,0 19 | halt 20 | end: 21 | .data 22 | """ 23 | 24 | src_bss = """\ 25 | .bss 26 | 27 | label: 28 | .long 0 29 | """ 30 | 31 | 32 | src_global = """\ 33 | 34 | .global counter 35 | counter: 36 | .long 0 37 | 38 | internal: 39 | .long 0 40 | 41 | .text 42 | .global entry 43 | entry: 44 | wait 42 45 | halt 46 | """ 47 | 48 | 49 | def test_parse_line(): 50 | a = Assembler() 51 | lines = iter(src.splitlines()) 52 | assert a.parse_line(next(lines)) == (None, '.set', ('const', '123', )) 53 | assert a.parse_line(next(lines)) == (None, '.set', ('const_left', '976', )) 54 | assert a.parse_line(next(lines)) == None 55 | assert a.parse_line(next(lines)) == ('start', 'wait', ('42', )) 56 | assert a.parse_line(next(lines)) == (None, 'ld', ('r0', 'r1', '0', )) 57 | assert a.parse_line(next(lines)) == (None, 'st', ('r0', 'r1', '0', )) 58 | assert a.parse_line(next(lines)) == (None, 'halt', ()) 59 | assert a.parse_line(next(lines)) == ('end', None, ()) 60 | assert a.parse_line(next(lines)) == (None, '.data', ()) # test left-aligned directive is not treated as label 61 | 62 | 63 | def test_parse_labels_correctly(): 64 | """ 65 | description of what defines a label 66 | https://sourceware.org/binutils/docs/as/Statements.html 67 | https://sourceware.org/binutils/docs/as/Labels.html 68 | """ 69 | a = Assembler() 70 | assert a.parse_line('') is None 71 | assert a.parse_line('label: .set const, 42') == ('label', '.set', ('const', '42',)) 72 | assert a.parse_line('label:.set const, 42') == ('label', '.set', ('const', '42',)) 73 | assert a.parse_line('label:') == ('label', None, ()) 74 | assert a.parse_line(' label:') == ('label', None, ()) 75 | assert a.parse_line(' label: ') == ('label', None, ()) 76 | assert a.parse_line('nop ') == (None, 'nop', ()) 77 | assert a.parse_line('.set c, 1 ') == (None, '.set', ('c', '1',)) 78 | assert a.parse_line('invalid : nop') == (None, 'invalid', (': nop',)) # no whitespace between label and colon 79 | assert a.parse_line('.string "hello world"') == (None, '.string', ('"hello world"',)) 80 | assert a.parse_line('.string "hello : world"') == (None, '.string', ('"hello : world"',)) # colon in string 81 | assert a.parse_line('label::') == ('label', ':', ()) 82 | assert a.parse_line('label: :') == ('label', ':', ()) 83 | assert a.parse_line('a_label:') == ('a_label', None, ()) 84 | assert a.parse_line('$label:') == ('$label', None, ()) 85 | assert a.parse_line('.label:') == ('.label', None, ()) 86 | assert a.parse_line('&label:') == (None, '&label:', ()) # & not a valid char in a label 87 | 88 | 89 | def test_parse(): 90 | a = Assembler() 91 | lines = remove_comments(src) 92 | result = a.parse(lines) 93 | assert None not in result 94 | 95 | 96 | def test_assemble(): 97 | a = Assembler() 98 | a.assemble(src) 99 | assert a.symbols.has_sym('const') 100 | assert a.symbols.has_sym('const_left') 101 | assert a.symbols.has_sym('start') 102 | assert a.symbols.has_sym('end') 103 | assert a.symbols.get_sym('const') == (ABS, None, 123) 104 | assert a.symbols.get_sym('const_left') == (ABS, None, 976) 105 | assert a.symbols.get_sym('start') == (REL, TEXT, 0) 106 | assert a.symbols.get_sym('end') == (REL, TEXT, 4) 107 | assert len(b''.join(a.sections[TEXT])) == 16 # 4 instructions * 4B 108 | assert len(a.sections[DATA]) == 0 109 | assert a.offsets[BSS] == 0 110 | 111 | 112 | def test_assemble_bss(): 113 | a = Assembler() 114 | try: 115 | a.assemble(src_bss) 116 | except TypeError: 117 | raised = True 118 | else: 119 | raised = False 120 | assert not raised 121 | assert a.offsets[BSS] == 4 # 1 word * 4B 122 | 123 | 124 | def test_assemble_bss_with_value(): 125 | lines = """\ 126 | .bss 127 | .long 3 #non-zero value not allowed in bss section 128 | """ 129 | 130 | a = Assembler() 131 | try: 132 | a.assemble(lines) 133 | except ValueError as e: 134 | if str(e) != "attempt to store non-zero value in section .bss": 135 | raise # re-raise failures we didn't expect 136 | raised = True 137 | else: 138 | raised = False 139 | 140 | assert raised 141 | 142 | 143 | def test_assemble_global(): 144 | a = Assembler() 145 | a.assemble(src_global) 146 | assert a.symbols.has_sym('counter') 147 | assert a.symbols.has_sym('internal') 148 | assert a.symbols.has_sym('entry') 149 | 150 | exported_symbols = a.symbols.export() 151 | assert exported_symbols == [(0, 'counter'), (2, 'entry')] # internal not exported 152 | 153 | exported_symbols = a.symbols.export(True) # include non-global symbols 154 | assert exported_symbols == [(0, 'counter'), (1, 'internal'), (2, 'entry')] 155 | 156 | 157 | def test_assemble_uppercase_opcode(): 158 | a = Assembler() 159 | try: 160 | a.assemble(" WAIT 42") 161 | except ValueError as e: 162 | if str(e) != "Unknown opcode or directive: WAIT": 163 | # re-raise failures we didn't expect 164 | raise 165 | raised = True 166 | else: 167 | raised = False 168 | assert not raised 169 | 170 | 171 | def test_assemble_evalulate_expressions(): 172 | src_w_expr = """\ 173 | .set shft, 2 174 | .set loops, (1 << shft) 175 | 176 | entry: 177 | move r0, 1+1 178 | move r1, loops 179 | move r2, (shft + 10) * 2 180 | move r3, entry << 2 181 | """ 182 | a = Assembler() 183 | a.assemble(src_w_expr) 184 | 185 | assert a.symbols.has_sym('shft') 186 | assert a.symbols.has_sym('loops') 187 | assert a.symbols.has_sym('entry') 188 | assert a.symbols.get_sym('shft') == (ABS, None, 2) 189 | assert a.symbols.get_sym('loops') == (ABS, None, 4) 190 | assert a.symbols.get_sym('entry') == (REL, TEXT, 0) 191 | 192 | 193 | def test_assemble_optional_comment_removal(): 194 | line = " move r1, 123 # comment" 195 | 196 | a = Assembler() 197 | 198 | # first assemble as normal (comments will be removed by default) 199 | a.assemble(line) 200 | 201 | # now assemble with comment removal disabled 202 | try: 203 | a.assemble(line, remove_comments=False) 204 | except ValueError as e: 205 | raised = True 206 | else: 207 | raised = False 208 | assert raised 209 | 210 | 211 | def test_assemble_test_regressions_from_evaluation(): 212 | line = " reg_wr (0x3ff48400 + 0x10), 1, 1, 1" 213 | 214 | a = Assembler() 215 | raised = False 216 | try: 217 | a.assemble(line) 218 | except ValueError as e: 219 | if str(e) == 'invalid register base': # ensure we trapped the expected Exception 220 | raised = True 221 | assert not raised 222 | 223 | 224 | def test_symbols(): 225 | st = SymbolTable({}, {}, {}) 226 | for entry in [ 227 | ('rel_t4', REL, TEXT, 4), 228 | ('abs_t4', ABS, TEXT, 4), 229 | ('rel_d4', REL, DATA, 4), 230 | ('abs_d4', ABS, DATA, 4), 231 | ('const', ABS, None, 123), 232 | ]: 233 | st.set_sym(*entry) 234 | # PASS 1 ======================================================== 235 | assert st.has_sym('abs_t4') 236 | assert st.get_sym('abs_t4') == (ABS, TEXT, 4) 237 | assert not st.has_sym('notexist') 238 | try: 239 | st.get_sym('notexist') # pass1 -> raises 240 | except KeyError: 241 | raised = True 242 | else: 243 | raised = False 244 | assert raised 245 | assert st.resolve_absolute('abs_t4') == 4 246 | try: 247 | # relative symbols cannot be resolved, because in pass 1 section bases are not yet defined 248 | st.resolve_absolute('rel_t4') 249 | except KeyError: 250 | raised = True 251 | else: 252 | raised = False 253 | assert raised 254 | assert st.resolve_absolute('const') == 123 255 | # PASS 2 ======================================================== 256 | st.set_bases({TEXT: 100, DATA: 200}) 257 | assert st.has_sym('abs_t4') 258 | assert st.get_sym('abs_t4') == (ABS, TEXT, 4) 259 | assert not st.has_sym('notexist') 260 | try: 261 | st.get_sym('notexist') # pass2 -> raises 262 | except KeyError: 263 | raised = True 264 | else: 265 | raised = False 266 | assert raised 267 | assert st.resolve_absolute('abs_t4') == 4 268 | assert st.resolve_absolute('abs_d4') == 4 269 | assert st.resolve_absolute('rel_t4') == 100 + 4 270 | assert st.resolve_absolute('rel_d4') == 200 + 4 271 | assert st.resolve_absolute('const') == 123 272 | st.set_from(TEXT, 8) 273 | assert st.resolve_relative('abs_t4') == 4 - 108 274 | assert st.resolve_relative('abs_d4') == 4 - 108 275 | assert st.resolve_relative('rel_t4') == 104 - 108 276 | assert st.resolve_relative('rel_d4') == 204 - 108 277 | assert st.resolve_absolute('const') == 123 278 | 279 | 280 | def test_support_multiple_statements_per_line(): 281 | src = """ 282 | label: nop; nop; 283 | wait 42 284 | """ 285 | 286 | lines = Assembler().parse(src.splitlines()) 287 | 288 | assert lines == [ 289 | ('label', 'nop', ()), 290 | (None, 'nop', ()), 291 | (None, 'wait', ('42',)) 292 | ] 293 | 294 | 295 | test_parse_line() 296 | test_parse_labels_correctly() 297 | test_parse() 298 | test_assemble() 299 | test_assemble_bss() 300 | test_assemble_bss_with_value() 301 | test_assemble_global() 302 | test_assemble_uppercase_opcode() 303 | test_assemble_evalulate_expressions() 304 | test_assemble_optional_comment_removal() 305 | test_assemble_test_regressions_from_evaluation() 306 | test_support_multiple_statements_per_line() 307 | test_symbols() 308 | -------------------------------------------------------------------------------- /tests/compat/alu.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .text 9 | 10 | and r1, r2, r3 11 | and r3, r0, 0xffff 12 | and r1, r1, 0xa5a5 13 | 14 | or r1, r2, r3 15 | or r3, r0, 0xffff 16 | or r1, r1, 0xa5a5 17 | 18 | add r1, r1, 32767 19 | add r0, r3, -32768 20 | add r3, r0, -1 21 | add r2, r1, 1 22 | 23 | sub r1, r1, 32767 24 | sub r0, r3, -32768 25 | sub r3, r0, -1 26 | sub r2, r1, 1 27 | 28 | lsh r0, r1, r2 29 | lsh r2, r3, 1 30 | 31 | rsh r3, r2, r1 32 | rsh r3, r2, 31 33 | 34 | move r0, r1 35 | move r0, 42 36 | 37 | stage_rst 38 | stage_inc 42 39 | stage_dec 23 40 | 41 | -------------------------------------------------------------------------------- /tests/compat/expr.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | # common example of real world code using expressions 9 | .set adc_channel, 6 10 | 11 | .set adc_oversampling_factor_log, 2 12 | .set adc_oversampling_factor, (1 << adc_oversampling_factor_log) 13 | 14 | .data 15 | 16 | result: 17 | .long 0 18 | 19 | .text 20 | .global entry 21 | entry: 22 | move r0, 0 23 | stage_rst 24 | 25 | measure: 26 | adc r1, 0, adc_channel + 1 27 | add r0, r0, r1 28 | 29 | stage_inc 1 30 | jumps measure, adc_oversampling_factor, lt 31 | 32 | rsh r0, r0, adc_oversampling_factor_log 33 | 34 | move r3, result 35 | st r0, r3, 0 36 | 37 | exit: 38 | halt 39 | 40 | 41 | # --- 42 | # test that expressions evaluate correctly for all supported operators 43 | # (these statements do not mean anything other than testing the operations) 44 | move r3, 1+2 45 | move r3, 3-5 46 | move r3, -5 47 | move r3, 2*3 48 | move r3, 4/2 49 | move r3, 4 % 3 50 | move r3, 0xff << 2 51 | move r3, 0xff >> 1 52 | move r3, (0xabcdef | 0xff) & 0xff 53 | move r3, 0x1234 & ~2 54 | move r3, 42|4&0xf # 46 (4&0xf is evaluated first) 55 | move r3, (42|4)&0xf # 14 (42|4 is evaluated first) 56 | 57 | # --- 58 | # test that expressions accept hex characters in either upper or lower case 59 | move r3, 0xaa - 1 60 | move r3, 0xBB - 1 61 | move r3, 0xCc - 1 62 | move r3, 0Xdd - 1 63 | move r3, 0XEF - 1 64 | -------------------------------------------------------------------------------- /tests/compat/fixes.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | # This file tests various fixes to the assembler, 9 | # to ensure the binary output matches that of binutils. 10 | # a) support for left-aligned directives (e.g. .set without preceding whitespace) 11 | # b) a crash-fix related to data items in the .bss section 12 | # c) support for marking labels as global 13 | # d) support for upper case ULP opcode names 14 | # 15 | .set gpio, 2 16 | 17 | .bss 18 | 19 | counter: 20 | .long 0 21 | 22 | .data 23 | var2: .int 1111 24 | 25 | .text 26 | .global entry 27 | entry: 28 | MOVE R1, gpio 29 | WAIT 42 30 | 31 | # reg_rd/reg_wr with "short" and "long" address notation 32 | reg_rd 12, 7, 0 33 | reg_rd 0x3ff48000, 7, 0 34 | 35 | # interpret ; as statement separator - this results in 2 NOP machine instructions 36 | nop; nop; 37 | 38 | # adc supports an undocumented 4th argument, which should be entirely ignored 39 | # binutils-esp32ulp also ignores this argument, if present, see: 40 | # https://github.com/espressif/binutils-esp32ulp/blob/249ec34cc2c9574a86f3f86bbb175a863f988bcf/gas/config/esp32ulp-parse.y#L810 41 | adc r1, 0, 1, 100 42 | 43 | halt 44 | -------------------------------------------------------------------------------- /tests/compat/io.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .text 9 | 10 | reg_rd 0x3ff48000, 7, 0 11 | reg_wr 0x3ff48000, 7, 0, 42 12 | 13 | i2c_rd 0x10, 7, 0, 0 14 | i2c_wr 0x23, 0x42, 7, 0, 1 15 | 16 | adc r0, 1, 2 17 | 18 | tsens r0, 42 19 | 20 | -------------------------------------------------------------------------------- /tests/compat/jumps.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .text 9 | .set const, 3 10 | .global const # exporting symbol is required for binutils, not important for micropython-esp32-ulp 11 | 12 | entry: 13 | nop 14 | 15 | # simple jumps 16 | jump entry 17 | jump later 18 | jump 0x120, EQ 19 | jump -288, EQ 20 | 21 | # jumps with labels 22 | jumps entry, 42, lt 23 | jumps entry, 42, lt 24 | jumps later, 42, lt 25 | jumps entry, 42, le 26 | jumps later, 42, le 27 | jumps entry, 42, ge 28 | jumps later, 42, ge 29 | jumps entry, 42, eq 30 | jumps later, 42, eq 31 | jumps entry, 42, gt 32 | jumps later, 42, gt 33 | 34 | # jumps with immediate offset (specified in bytes, but real instruction uses words) 35 | jumps 0, 42, lt 36 | jumps 0, 42, eq # dual-instruction condition 37 | 38 | jumps 4, 42, lt 39 | jumps 4, 42, eq # dual-instruction condition 40 | jumps 8, 42, lt 41 | jumps 32, 42, lt 42 | 43 | jumps -4, 42, lt 44 | jumps -4, 42, eq # dual-instruction condition 45 | jumps -8, 42, lt 46 | jumps -32, 42, lt 47 | 48 | # jumps with immediate offset from absolute symbol 49 | jumps const, 42, lt 50 | 51 | # jumpr with labels 52 | jumpr entry, 42, lt 53 | jumpr later, 42, lt 54 | jumpr entry, 42, ge 55 | jumpr later, 42, ge 56 | jumpr entry, 42, le 57 | jumpr later, 42, le 58 | jumpr entry, 42, gt 59 | jumpr later, 42, gt 60 | jumpr entry, 42, eq 61 | jumpr later, 42, eq 62 | 63 | # jumpr with immediate offset (specified in bytes, but real instruction uses words) 64 | jumpr 0, 42, lt 65 | jumpr 0, 42, eq # dual-instruction condition 66 | 67 | jumpr 4, 42, lt 68 | jumpr 4, 42, eq # dual-instruction condition 69 | jumpr 8, 42, lt 70 | jumpr 32, 42, lt 71 | 72 | jumpr -4, 42, lt 73 | jumpr -4, 42, eq # dual-instruction condition 74 | jumpr -8, 42, lt 75 | jumpr -32, 42, lt 76 | 77 | # jumps with immediate offset from absolute symbol 78 | jumpr const, 42, lt 79 | 80 | nop 81 | nop 82 | nop 83 | later: 84 | -------------------------------------------------------------------------------- /tests/compat/loadstore.esp32s2.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .set offs, 0x20 9 | .set lab1, 0x01 10 | 11 | .text 12 | LDL R1, R2, 0x20 13 | LDL R1, R2, offs 14 | LDH R1, R2, 0x20 15 | LDH R1, R2, offs 16 | 17 | STL R1, R2, 0x20 18 | STL R1, R2, offs 19 | STL R1, R2, offs, 1 20 | STL R1, R2, offs, lab1 21 | 22 | STH R1, R2, 0x20 23 | STH R1, R2, offs 24 | STH R1, R2, offs, 1 25 | STH R1, R2, offs, lab1 26 | 27 | ST32 R1, R2, 0x10, 1 28 | ST32 R1, R2, offs, lab1 29 | 30 | STI32 R1, R2, 1 31 | STI32 R1, R2, lab1 32 | 33 | STI R1, R2 34 | STI R1, R2, 1 35 | STI R1, R2, lab1 36 | 37 | STO 0x20 38 | STO offs 39 | -------------------------------------------------------------------------------- /tests/compat/memory.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .text 9 | 10 | ld r0, r1, 0 11 | ld r2, r3, 4 12 | 13 | st r0, r1, 0 14 | st r3, r2, 8 15 | 16 | -------------------------------------------------------------------------------- /tests/compat/preprocess_simple.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | #define GPIO 2 9 | #define BASE 0x100 10 | #define ADDR (BASE + GPIO) 11 | 12 | entry: 13 | move r0, GPIO 14 | move r1, ADDR 15 | -------------------------------------------------------------------------------- /tests/compat/reg.esp32.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | #include "soc/rtc_cntl_reg.h" 9 | #include "soc/soc_ulp.h" 10 | 11 | reg_rd 0x012, 1, 2 12 | reg_rd 0x234, 3, 4 13 | reg_rd 0x345, 5, 6 14 | 15 | reg_wr 0x012, 1, 2, 1 16 | reg_wr 0x234, 3, 4, 1 17 | reg_wr 0x345, 5, 6, 1 18 | 19 | WRITE_RTC_REG(0x3ff484a8, 1, 2, 3) 20 | READ_RTC_REG(0x3ff484a8, 1, 2) 21 | WRITE_RTC_REG(0x3ff48904, 1, 2, 3) 22 | READ_RTC_REG(0x3ff48904, 1, 2) 23 | -------------------------------------------------------------------------------- /tests/compat/reg.esp32s2.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | #include "soc/rtc_cntl_reg.h" 9 | #include "soc/soc_ulp.h" 10 | 11 | reg_rd 0x012, 1, 2 12 | reg_rd 0x234, 3, 4 13 | reg_rd 0x345, 5, 6 14 | 15 | reg_wr 0x012, 1, 2, 1 16 | reg_wr 0x234, 3, 4, 1 17 | reg_wr 0x345, 5, 6, 1 18 | 19 | WRITE_RTC_REG(0x3f4084a8, 1, 2, 3) 20 | READ_RTC_REG(0x3f4084a8, 1, 2) 21 | WRITE_RTC_REG(0x3f408904, 1, 2, 3) 22 | READ_RTC_REG(0x3f408904, 1, 2) 23 | -------------------------------------------------------------------------------- /tests/compat/sections.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .text 9 | 10 | nop 11 | nop 12 | nop 13 | 14 | .data 15 | 16 | .space 4 17 | .space 8, 0xFF 18 | .space 1 19 | .align 4 20 | .space 3 21 | 22 | # a section start will be automatically 32bit-aligned: 23 | .bss 24 | 25 | .space 10 26 | # a section end will be automatically 32bit-aligned 27 | -------------------------------------------------------------------------------- /tests/compat/sleep.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .text 9 | 10 | nop 11 | wait 1000 12 | 13 | wake 14 | sleep 1 15 | 16 | halt 17 | -------------------------------------------------------------------------------- /tests/compat/symbols.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .text 9 | 10 | .set constant42, 42 11 | .set notindented, 1 12 | 13 | start: move r0, data0 14 | move r1, data1 15 | move r2, constant42 16 | move r3, notindented 17 | 18 | # count from 0 .. 42 in stage register 19 | stage_rst 20 | loop1: stage_inc 1 21 | jumps loop1, 42, lt 22 | 23 | # count from 0 .. 42 in r0 24 | move r0, 0 25 | loop2: add r0, r0, 1 26 | jumpr loop2, 42, lt 27 | 28 | end: jump start 29 | 30 | .data 31 | 32 | data0: .long 1000 33 | data1: .long 1001 34 | 35 | -------------------------------------------------------------------------------- /tests/decode_s2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from tools.decode_s2 import decode_instruction, get_instruction_fields 9 | import esp32_ulp.opcodes_s2 as opcodes 10 | import ubinascii 11 | 12 | tests = [] 13 | 14 | 15 | def test(param): 16 | tests.append(param) 17 | 18 | 19 | def hex_to_int(sequence): 20 | byte_sequence = ubinascii.unhexlify(sequence) 21 | return int.from_bytes(byte_sequence, 'little') 22 | 23 | 24 | def assert_decode(sequence, expected_struct, expected_name): 25 | i = hex_to_int(sequence) 26 | 27 | ins, name = decode_instruction(i) 28 | 29 | assert name == expected_name, '%s != %s' % (name, expected_name) 30 | assert ins is expected_struct, 'incorrect instruction struct (%s, %s)' % (sequence, name) 31 | 32 | 33 | def assert_decode_exception(sequence, expected_message): 34 | i = hex_to_int(sequence) 35 | 36 | try: 37 | decode_instruction(i) 38 | except Exception as e: 39 | assert str(e) == expected_message, str(e) 40 | raised = True 41 | else: 42 | raised = False 43 | 44 | assert raised, 'Exception not raised' 45 | 46 | 47 | def assert_decode_fields(sequence, expected_field_details): 48 | i = hex_to_int(sequence) 49 | 50 | ins, _ = decode_instruction(i) 51 | 52 | actual_field_details = get_instruction_fields(ins) 53 | 54 | assert actual_field_details == expected_field_details, '\n- %s \n+ %s' % (actual_field_details, expected_field_details) 55 | 56 | 57 | @test 58 | def test_unknown_instruction(): 59 | assert_decode_exception("10000001", 'Unknown instruction') 60 | 61 | 62 | @test 63 | def test_empty_instruction(): 64 | assert_decode_exception("00000000", '') 65 | 66 | 67 | # All hex sequences were generated using our assembler. 68 | # Note: disassembled instructions always show field values according 69 | # to what is actually encoded into the binary instruction, not as per 70 | # original assembly code. 71 | # For example in JUMP instructions in the source code one would 72 | # specify jump offsets in bytes (e.g. 4 bytes) but in the actual 73 | # instruction offset encoded in the binary instruction will be in 74 | # words (1 word = 4 bytes). 75 | # The disassembled instructions would therefore show as "JUMP 1" 76 | # for what was originally "JUMP 4" in the source code.@test 77 | @test 78 | def test_all_instructions(): 79 | # OPCODE_WR_REG = 1 80 | assert_decode("00000010", opcodes._wr_reg, 'REG_WR 0x0, 0, 0, 0') 81 | 82 | # OPCODE_RD_REG = 2 83 | assert_decode("00000020", opcodes._rd_reg, 'REG_RD 0x0, 0, 0') 84 | 85 | # OPCODE_I2C = 3 86 | assert_decode("00000030", opcodes._i2c, 'I2C_RD 0, 0, 0, 0') 87 | assert_decode("00000038", opcodes._i2c, 'I2C_WR 0, 0, 0, 0') 88 | 89 | # OPCODE_DELAY = 4 90 | assert_decode("00000040", opcodes._delay, 'NOP') 91 | assert_decode("01000040", opcodes._delay, 'WAIT 1') 92 | 93 | # OPCODE_ADC = 5 94 | assert_decode("00000050", opcodes._adc, 'ADC r0, 0, 0') 95 | 96 | # OPCODE_ST = 6, SUB_OPCODE_ST 97 | assert_decode("80010068", opcodes._st, 'ST r0, r0, 0') 98 | assert_decode("c0010068", opcodes._st, 'STH r0, r0, 0') 99 | assert_decode("90000068", opcodes._st, 'STL r0, r0, 0, 1') 100 | assert_decode("d0000068", opcodes._st, 'STH r0, r0, 0, 1') 101 | assert_decode("00000068", opcodes._st, 'ST32 r0, r0, 0, 0') 102 | assert_decode("10000068", opcodes._st, 'ST32 r0, r0, 0, 1') 103 | 104 | # OPCODE_ST = 6, SUB_OPCODE_ST_AUTO 105 | assert_decode("80010062", opcodes._st, 'STI r0, r0') 106 | assert_decode("90000062", opcodes._st, 'STI r0, r0, 1') 107 | assert_decode("00000062", opcodes._st, 'STI32 r0, r0, 0') 108 | assert_decode("10000062", opcodes._st, 'STI32 r0, r0, 1') 109 | 110 | # OPCODE_ST = 6, SUB_OPCODE_ST_OFFSET 111 | assert_decode("00000064", opcodes._st, 'STO 0') 112 | assert_decode("00040064", opcodes._st, 'STO 1') 113 | 114 | # OPCODE_ALU = 7, SUB_OPCODE_ALU_REG 115 | assert_decode("00000070", opcodes._alu_reg, 'ADD r0, r0, r0') 116 | assert_decode("00002070", opcodes._alu_reg, 'SUB r0, r0, r0') 117 | assert_decode("00004070", opcodes._alu_reg, 'AND r0, r0, r0') 118 | assert_decode("00006070", opcodes._alu_reg, 'OR r0, r0, r0') 119 | assert_decode("00008070", opcodes._alu_reg, "MOVE r0, r0") 120 | assert_decode("0000a070", opcodes._alu_reg, 'LSH r0, r0, r0') 121 | assert_decode("0000c070", opcodes._alu_reg, 'RSH r0, r0, r0') 122 | 123 | # OPCODE_ALU = 7, SUB_OPCODE_ALU_IMM 124 | assert_decode("00000074", opcodes._alu_imm, 'ADD r0, r0, 0') 125 | assert_decode("00002074", opcodes._alu_imm, 'SUB r0, r0, 0') 126 | assert_decode("00004074", opcodes._alu_imm, 'AND r0, r0, 0') 127 | assert_decode("00006074", opcodes._alu_imm, 'OR r0, r0, 0') 128 | assert_decode("00008074", opcodes._alu_imm, "MOVE r0, 0") 129 | assert_decode("0000a074", opcodes._alu_imm, 'LSH r0, r0, 0') 130 | assert_decode("0000c074", opcodes._alu_imm, 'RSH r0, r0, 0') 131 | 132 | # OPCODE_ALU = 7, SUB_OPCODE_ALU_CNT 133 | assert_decode("00004078", opcodes._alu_cnt, 'STAGE_RST') 134 | assert_decode("00000078", opcodes._alu_cnt, 'STAGE_INC 0') 135 | assert_decode("00002078", opcodes._alu_cnt, 'STAGE_DEC 0') 136 | 137 | # OPCODE_BRANCH = 8, SUB_OPCODE_BX (IMM) 138 | assert_decode("00000084", opcodes._bx, 'JUMP 0') 139 | assert_decode("00004084", opcodes._bx, 'JUMP 0, EQ') 140 | assert_decode("00008084", opcodes._bx, 'JUMP 0, OV') 141 | 142 | # OPCODE_BRANCH = 8, SUB_OPCODE_BX (REG) 143 | assert_decode("00002084", opcodes._bx, 'JUMP r0') 144 | assert_decode("00006084", opcodes._bx, 'JUMP r0, EQ') 145 | assert_decode("0000a084", opcodes._bx, 'JUMP r0, OV') 146 | 147 | # OPCODE_BRANCH = 8, SUB_OPCODE_BR 148 | assert_decode("00000080", opcodes._b, 'JUMPR 0, 0, LT') 149 | assert_decode("00000180", opcodes._b, 'JUMPR 0, 0, GT') 150 | assert_decode("00000280", opcodes._b, 'JUMPR 0, 0, EQ') 151 | 152 | # OPCODE_BRANCH = 8, SUB_OPCODE_BX 153 | assert_decode("00800088", opcodes._bs, 'JUMPS 0, 0, LT') 154 | assert_decode("00800188", opcodes._bs, 'JUMPS 0, 0, GT') 155 | assert_decode("00000288", opcodes._bs, 'JUMPS 0, 0, EQ') 156 | assert_decode("00800288", opcodes._bs, 'JUMPS 0, 0, LE') 157 | assert_decode("00800388", opcodes._bs, 'JUMPS 0, 0, GE') 158 | 159 | # OPCODE_END = 9, SUB_OPCODE_END 160 | assert_decode("01000090", opcodes._end, 'WAKE') 161 | 162 | # OPCODE_END = 9, SUB_OPCODE_SLEEP 163 | ###assert_decode("01000040", opcodes._end, 'SLEEP 1') ##TODO 164 | 165 | # OPCODE_TSENS = 10 166 | assert_decode("000000a0", opcodes._tsens, 'TSENS r0, 0') 167 | 168 | # OPCODE_HALT = 11 169 | assert_decode("000000b0", opcodes._halt, 'HALT') 170 | 171 | # OPCODE_LD = 13 172 | assert_decode("000000d0", opcodes._ld, 'LD r0, r0, 0') 173 | 174 | 175 | if __name__ == '__main__': 176 | # run all methods marked with @test 177 | for t in tests: 178 | t() 179 | -------------------------------------------------------------------------------- /tests/definesdb.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | import os 9 | 10 | from esp32_ulp.definesdb import DefinesDB, DBNAME 11 | from esp32_ulp.util import file_exists 12 | 13 | tests = [] 14 | 15 | 16 | def test(param): 17 | tests.append(param) 18 | 19 | 20 | @test 21 | def test_definesdb_clear_removes_all_keys(): 22 | db = DefinesDB() 23 | db.open() 24 | db.update({'KEY1': 'VALUE1'}) 25 | 26 | db.clear() 27 | 28 | assert 'KEY1' not in db 29 | 30 | db.close() 31 | 32 | 33 | @test 34 | def test_definesdb_persists_data_across_instantiations(): 35 | db = DefinesDB() 36 | db.open() 37 | db.clear() 38 | 39 | db.update({'KEY1': 'VALUE1'}) 40 | 41 | assert 'KEY1' in db 42 | 43 | db.close() 44 | del db 45 | db = DefinesDB() 46 | db.open() 47 | 48 | assert db.get('KEY1', None) == 'VALUE1' 49 | 50 | db.close() 51 | 52 | 53 | @test 54 | def test_definesdb_should_not_create_a_db_file_when_only_reading(): 55 | db = DefinesDB() 56 | 57 | db.clear() 58 | assert not file_exists(DBNAME) 59 | 60 | assert db.get('some-key', None) is None 61 | assert not file_exists(DBNAME) 62 | 63 | 64 | if __name__ == '__main__': 65 | # run all methods marked with @test 66 | for t in tests: 67 | t() 68 | -------------------------------------------------------------------------------- /tests/esp32.ulp.ld: -------------------------------------------------------------------------------- 1 | ULP_BIN_MAGIC = 0x00706c75; 2 | HEADER_SIZE = 12; 3 | CONFIG_ULP_COPROC_RESERVE_MEM = 4096; 4 | 5 | MEMORY 6 | { 7 | ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM 8 | } 9 | 10 | SECTIONS 11 | { 12 | .text : AT(HEADER_SIZE) 13 | { 14 | *(.text) 15 | } >ram 16 | .data : 17 | { 18 | . = ALIGN(4); 19 | *(.data) 20 | } >ram 21 | .bss : 22 | { 23 | . = ALIGN(4); 24 | *(.bss) 25 | } >ram 26 | 27 | .header : AT(0) 28 | { 29 | LONG(ULP_BIN_MAGIC) 30 | SHORT(LOADADDR(.text)) 31 | SHORT(SIZEOF(.text)) 32 | SHORT(SIZEOF(.data)) 33 | SHORT(SIZEOF(.bss)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/esp32_ulp: -------------------------------------------------------------------------------- 1 | ../esp32_ulp -------------------------------------------------------------------------------- /tests/fixtures/all_opcodes.esp32.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .data 9 | empty: .long 0 10 | magic: .long 0xc0decafe 11 | 12 | .text 13 | REG_WR 0x123, 1, 2, 3 14 | 15 | REG_RD 0x321, 2, 1 16 | 17 | I2C_RD 3, 2, 1, 0 18 | I2C_WR 0, 1, 2, 3, 4 19 | 20 | NOP 21 | WAIT 7 22 | 23 | ADC r3, 2, 1 24 | 25 | ST r3, r2, 1 26 | 27 | ADD r2, r1, r0 28 | SUB r2, r1, r0 29 | AND r2, r1, r0 30 | OR r2, r1, r0 31 | MOVE r2, r1 32 | LSH r2, r1, r0 33 | RSH r2, r1, r0 34 | 35 | ADD r2, r1, 0 36 | SUB r2, r1, 0 37 | AND r2, r1, 0 38 | OR r2, r1, 0 39 | MOVE r1, 0 40 | LSH r2, r1, 0 41 | RSH r2, r1, 0 42 | 43 | STAGE_RST 44 | STAGE_INC 7 45 | STAGE_DEC 3 46 | 47 | JUMP r0 48 | JUMP r1, EQ 49 | JUMP r2, OV 50 | 51 | JUMP 0 52 | JUMP 0, EQ 53 | JUMP 0, OV 54 | 55 | JUMPR 0, 1, LT 56 | JUMPR 4, 5, GT 57 | JUMPR 8, 7, EQ 58 | 59 | JUMPS 0, 1, LT 60 | JUMPS 4, 5, GT 61 | JUMPS 8, 7, EQ 62 | JUMPS 12, 9, LE 63 | JUMPS 16, 11, GE 64 | 65 | WAKE 66 | SLEEP 7 67 | 68 | TSENS r1, 2 69 | 70 | HALT 71 | 72 | LD r2, r1, 0 73 | -------------------------------------------------------------------------------- /tests/fixtures/all_opcodes.esp32.lst: -------------------------------------------------------------------------------- 1 | .text 2 | 0000 230d8810 REG_WR 0x123, 1, 2, 3 3 | 0004 21030421 REG_RD 0x321, 2, 1 4 | 0008 03001130 I2C_RD 3, 2, 1, 0 5 | 000c 00011339 I2C_WR 0, 2, 3, 4 6 | 0010 00000040 NOP 7 | 0014 07000040 WAIT 7 8 | 0018 07000050 ADC r3, 1, 0 9 | 001c 0b000068 ST r3, r2, 0 10 | 0020 06000070 ADD r2, r1, r0 11 | 0024 06002070 SUB r2, r1, r0 12 | 0028 06004070 AND r2, r1, r0 13 | 002c 06006070 OR r2, r1, r0 14 | 0030 16008070 MOVE r2, r1 15 | 0034 0600a070 LSH r2, r1, r0 16 | 0038 0600c070 RSH r2, r1, r0 17 | 003c 06000072 ADD r2, r1, 0 18 | 0040 06002072 SUB r2, r1, 0 19 | 0044 06004072 AND r2, r1, 0 20 | 0048 06006072 OR r2, r1, 0 21 | 004c 01008072 MOVE r1, 0 22 | 0050 0600a072 LSH r2, r1, 0 23 | 0054 0600c072 RSH r2, r1, 0 24 | 0058 00004074 STAGE_RST 25 | 005c 70000074 STAGE_INC 7 26 | 0060 30002074 STAGE_DEC 3 27 | 0064 00002080 JUMP r0 28 | 0068 01006080 JUMP r1, EQ 29 | 006c 0200a080 JUMP r2, OV 30 | 0070 00000080 JUMP 0 31 | 0074 00004080 JUMP 0, EQ 32 | 0078 00008080 JUMP 0, OV 33 | 007c 01000082 JUMPR 0, 1, LT 34 | 0080 06000382 JUMPR 1, 6, GE 35 | 0084 08000582 JUMPR 2, 8, GE 36 | 0088 07000582 JUMPR 2, 7, GE 37 | 008c 01000084 JUMPS 0, 1, LT 38 | 0090 05000584 JUMPS 2, 5, LE 39 | 0094 05800284 JUMPS 1, 5, GE 40 | 0098 07000484 JUMPS 2, 7, LT 41 | 009c 07000584 JUMPS 2, 7, LE 42 | 00a0 09000784 JUMPS 3, 9, LE 43 | 00a4 0b800884 JUMPS 4, 11, GE 44 | 00a8 01000090 WAKE 45 | 00ac 07000092 SLEEP 7 46 | 00b0 090000a0 TSENS r1, 2 47 | 00b4 000000b0 HALT 48 | 00b8 060000d0 LD r2, r1, 0 49 | .data 50 | 00bc 00000000 51 | 00c0 fecadec0 52 | -------------------------------------------------------------------------------- /tests/fixtures/all_opcodes.esp32s2.S: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | .data 9 | empty: .long 0 10 | magic: .long 0xc0decafe 11 | 12 | .text 13 | REG_WR 0x123, 1, 2, 3 14 | 15 | REG_RD 0x321, 2, 1 16 | 17 | I2C_RD 3, 2, 1, 0 18 | I2C_WR 0, 1, 2, 3, 4 19 | 20 | NOP 21 | WAIT 7 22 | 23 | ADC r3, 2, 1 24 | 25 | ST r3, r2, 1 26 | 27 | ADD r2, r1, r0 28 | SUB r2, r1, r0 29 | AND r2, r1, r0 30 | OR r2, r1, r0 31 | MOVE r2, r1 32 | LSH r2, r1, r0 33 | RSH r2, r1, r0 34 | 35 | ADD r2, r1, 0 36 | SUB r2, r1, 0 37 | AND r2, r1, 0 38 | OR r2, r1, 0 39 | MOVE r1, 0 40 | LSH r2, r1, 0 41 | RSH r2, r1, 0 42 | 43 | STAGE_RST 44 | STAGE_INC 7 45 | STAGE_DEC 3 46 | 47 | JUMP r0 48 | JUMP r1, EQ 49 | JUMP r2, OV 50 | 51 | JUMP 0 52 | JUMP 0, EQ 53 | JUMP 0, OV 54 | 55 | JUMPR 0, 1, LT 56 | JUMPR 4, 5, GT 57 | JUMPR 8, 7, EQ 58 | 59 | JUMPS 0, 1, LT 60 | JUMPS 4, 5, GT 61 | JUMPS 8, 7, EQ 62 | JUMPS 12, 9, LE 63 | JUMPS 16, 11, GE 64 | 65 | WAKE 66 | SLEEP 7 67 | 68 | TSENS r1, 2 69 | 70 | HALT 71 | 72 | LD r2, r1, 0 73 | 74 | # ESP32-S2 specific instructions 75 | NOP # marker 76 | 77 | LDL R1, R2, 0x20 78 | LDH R1, R2, 0x20 79 | 80 | STL R1, R2, 0x20 81 | STL R1, R2, 0x20, 0 82 | STL R1, R2, 0x20, 1 83 | 84 | STH R1, R2, 0x20 85 | STH R1, R2, 0x20, 0 86 | STH R1, R2, 0x20, 1 87 | 88 | ST32 R1, R2, 0x20, 0 89 | ST32 R1, R2, 0x20, 1 90 | 91 | STI R1, R2 92 | STI R1, R2, 0 93 | STI R1, R2, 1 94 | 95 | STI32 R1, R2, 0 96 | STI32 R1, R2, 1 97 | 98 | STO 0x20 99 | 100 | LDL R1, R2, -0x20 101 | LDH R1, R2, -0x20 102 | 103 | STL R1, R2, -0x20 104 | STH R1, R2, -0x20 105 | -------------------------------------------------------------------------------- /tests/fixtures/all_opcodes.esp32s2.lst: -------------------------------------------------------------------------------- 1 | .text 2 | 0000 230d8810 REG_WR 0x123, 1, 2, 3 3 | 0004 21030421 REG_RD 0x321, 2, 1 4 | 0008 03001130 I2C_RD 3, 2, 1, 0 5 | 000c 00011339 I2C_WR 0, 2, 3, 4 6 | 0010 00000040 NOP 7 | 0014 07000040 WAIT 7 8 | 0018 07000050 ADC r3, 1, 0 9 | 001c 8b010068 ST r3, r2, 0 10 | 0020 06000070 ADD r2, r1, r0 11 | 0024 06002070 SUB r2, r1, r0 12 | 0028 06004070 AND r2, r1, r0 13 | 002c 06006070 OR r2, r1, r0 14 | 0030 16008070 MOVE r2, r1 15 | 0034 0600a070 LSH r2, r1, r0 16 | 0038 0600c070 RSH r2, r1, r0 17 | 003c 06000074 ADD r2, r1, 0 18 | 0040 06002074 SUB r2, r1, 0 19 | 0044 06004074 AND r2, r1, 0 20 | 0048 06006074 OR r2, r1, 0 21 | 004c 01008074 MOVE r1, 0 22 | 0050 0600a074 LSH r2, r1, 0 23 | 0054 0600c074 RSH r2, r1, 0 24 | 0058 00004078 STAGE_RST 25 | 005c 70000078 STAGE_INC 7 26 | 0060 30002078 STAGE_DEC 3 27 | 0064 00002084 JUMP r0 28 | 0068 01006084 JUMP r1, EQ 29 | 006c 0200a084 JUMP r2, OV 30 | 0070 00000084 JUMP 0 31 | 0074 00004084 JUMP 0, EQ 32 | 0078 00008084 JUMP 0, OV 33 | 007c 01000080 JUMPR 0, 1, LT 34 | 0080 05000580 JUMPR 1, 5, GT 35 | 0084 07000a80 JUMPR 2, 7, EQ 36 | 0088 01800088 JUMPS 0, 1, LT 37 | 008c 05801188 JUMPS 4, 5, GT 38 | 0090 07002288 JUMPS 8, 7, EQ 39 | 0094 09803288 JUMPS 12, 9, LE 40 | 0098 0b804388 JUMPS 16, 11, GE 41 | 009c 01000090 WAKE 42 | 00a0 07000040 WAIT 7 43 | 00a4 090000a0 TSENS r1, 2 44 | 00a8 000000b0 HALT 45 | 00ac 060000d0 LD r2, r1, 0 46 | 00b0 00000040 NOP 47 | 00b4 092000d0 LD r1, r2, 8 48 | 00b8 092000d8 LDH r1, r2, 8 49 | 00bc 89210068 ST r1, r2, 8 50 | 00c0 89200068 ST r1, r2, 8 51 | 00c4 99200068 STL r1, r2, 8, 1 52 | 00c8 c9210068 STH r1, r2, 8 53 | 00cc c9200068 STH r1, r2, 8 54 | 00d0 d9200068 STH r1, r2, 8, 1 55 | 00d4 09200068 ST32 r1, r2, 8, 0 56 | 00d8 19200068 ST32 r1, r2, 8, 1 57 | 00dc 89010062 STI r1, r2 58 | 00e0 89000062 STI r1, r2 59 | 00e4 99000062 STI r1, r2, 1 60 | 00e8 09000062 STI32 r1, r2, 0 61 | 00ec 19000062 STI32 r1, r2, 1 62 | 00f0 00200064 STO 8 63 | 00f4 09e01fd0 LD r1, r2, -8 64 | 00f8 09e01fd8 LDH r1, r2, -8 65 | 00fc 89e11f68 ST r1, r2, -8 66 | 0100 c9e11f68 STH r1, r2, -8 67 | .data 68 | 0104 00000000 69 | 0108 fecadec0 70 | -------------------------------------------------------------------------------- /tests/fixtures/incl.h: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | #define CONST1 42 9 | #define MACRO(x,y) x+y 10 | #define MULTI_LINE abc \ 11 | xyz 12 | #define CONST2 99 13 | -------------------------------------------------------------------------------- /tests/fixtures/incl2.h: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | #define CONST2 123 9 | #define CONST3 777 10 | -------------------------------------------------------------------------------- /tests/fixtures/manual_bytes-v.esp32.lst: -------------------------------------------------------------------------------- 1 | 0000 e1af8c72 MOVE r1, 51966 2 | dreg = 1 3 | imm = 51966 (0xcafe) 4 | opcode = 7 5 | sel = 4 (MOVE) 6 | sreg = 0 7 | sub_opcode = 1 8 | unused = 0 9 | 0004 01000068 ST r1, r0, 0 10 | dreg = 0 11 | offset = 0 12 | opcode = 6 13 | sreg = 1 14 | sub_opcode = 4 15 | unused1 = 0 16 | unused2 = 0 17 | 0008 2705cc19 REG_WR 0x127, 19, 19, 1 18 | addr = 39 (0x27) 19 | data = 1 20 | high = 19 (0x13) 21 | low = 19 (0x13) 22 | opcode = 1 23 | periph_sel = 1 24 | 000c 0005681d REG_WR 0x100, 26, 26, 1 25 | addr = 0 26 | data = 1 27 | high = 26 (0x1a) 28 | low = 26 (0x1a) 29 | opcode = 1 30 | periph_sel = 1 31 | 0010 000000a0 TSENS r0, 0 32 | delay = 0 33 | dreg = 0 34 | opcode = 10 (0x0a) 35 | unused = 0 36 | 0014 00000074 STAGE_INC 0 37 | imm = 0 38 | opcode = 7 39 | sel = 0 (STAGE_INC) 40 | sub_opcode = 2 41 | unused1 = 0 42 | unused2 = 0 43 | -------------------------------------------------------------------------------- /tests/fixtures/manual_bytes-v.esp32s2.lst: -------------------------------------------------------------------------------- 1 | 0000 e1af8c74 MOVE r1, 51966 2 | dreg = 1 3 | imm = 51966 (0xcafe) 4 | opcode = 7 5 | sel = 4 (MOVE) 6 | sreg = 0 7 | sub_opcode = 1 8 | unused1 = 0 9 | unused2 = 0 10 | 0004 81010068 ST r1, r0, 0 11 | dreg = 0 12 | offset = 0 13 | opcode = 6 14 | sreg = 1 15 | sub_opcode = 4 16 | unused1 = 0 17 | unused2 = 0 18 | label = 0 19 | upper = 0 20 | wr_way = 3 21 | 0008 2705cc19 REG_WR 0x127, 19, 19, 1 22 | addr = 39 (0x27) 23 | data = 1 24 | high = 19 (0x13) 25 | low = 19 (0x13) 26 | opcode = 1 27 | periph_sel = 1 28 | 000c 0005681d REG_WR 0x100, 26, 26, 1 29 | addr = 0 30 | data = 1 31 | high = 26 (0x1a) 32 | low = 26 (0x1a) 33 | opcode = 1 34 | periph_sel = 1 35 | 0010 000000a0 TSENS r0, 0 36 | delay = 0 37 | dreg = 0 38 | opcode = 10 (0x0a) 39 | unused = 0 40 | 0014 00000078 STAGE_INC 0 41 | imm = 0 42 | opcode = 7 43 | sel = 0 (STAGE_INC) 44 | sub_opcode = 2 45 | unused1 = 0 46 | unused2 = 0 47 | -------------------------------------------------------------------------------- /tests/fixtures/manual_bytes.esp32.lst: -------------------------------------------------------------------------------- 1 | 0000 e1af8c72 MOVE r1, 51966 2 | 0004 01000068 ST r1, r0, 0 3 | 0008 2705cc19 REG_WR 0x127, 19, 19, 1 4 | 000c 0005681d REG_WR 0x100, 26, 26, 1 5 | 0010 000000a0 TSENS r0, 0 6 | 0014 00000074 STAGE_INC 0 7 | -------------------------------------------------------------------------------- /tests/fixtures/manual_bytes.esp32s2.lst: -------------------------------------------------------------------------------- 1 | 0000 e1af8c74 MOVE r1, 51966 2 | 0004 81010068 ST r1, r0, 0 3 | 0008 2705cc19 REG_WR 0x127, 19, 19, 1 4 | 000c 0005681d REG_WR 0x100, 26, 26, 1 5 | 0010 000000a0 TSENS r0, 0 6 | 0014 00000078 STAGE_INC 0 7 | -------------------------------------------------------------------------------- /tests/link.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from esp32_ulp.link import make_binary 9 | 10 | 11 | def test_make_binary(): 12 | text = b'\x12\x34\x56\x78' 13 | data = b'\x11\x22\x33\x44' 14 | bss_size = 40 15 | bin = make_binary(text, data, bss_size) 16 | assert len(bin) == 12 + len(text) + len(data) 17 | assert bin.startswith(b'\x75\x6c\x70\x00') # magic, LE 18 | assert bin.endswith(text+data) 19 | 20 | 21 | test_make_binary() 22 | 23 | -------------------------------------------------------------------------------- /tests/nocomment.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from esp32_ulp.nocomment import remove_comments 9 | 10 | ORIG = """\ 11 | /* 12 | * HEADER 13 | */ 14 | 15 | # single, full line comment 16 | 17 | label: // another rest-of-line comment 18 | 19 | nop /* partial line */ 20 | 21 | .string "try confusing /* with a comment start" 22 | .string "should be there 1" /* another comment */ 23 | .string 'try confusing */ with a comment end' 24 | 25 | .string "more confusing \\" /* should be there 2" 26 | /* comment */ 27 | .string 'more confusing \\' */' 28 | 29 | /***** FOOTER *****/ 30 | """ 31 | 32 | EXPECTED = """\ 33 | 34 | 35 | 36 | 37 | 38 | 39 | label: 40 | 41 | nop 42 | 43 | .string "try confusing /* with a comment start" 44 | .string "should be there 1" 45 | .string 'try confusing */ with a comment end' 46 | 47 | .string "more confusing \\" /* should be there 2" 48 | 49 | .string 'more confusing \\' */' 50 | 51 | 52 | """ 53 | 54 | 55 | def test_remove_comments(): 56 | lines_orig = ORIG.splitlines() 57 | len_orig = len(lines_orig) 58 | lines_expected = EXPECTED.splitlines() 59 | len_expected = len(lines_expected) 60 | lines_got = remove_comments(ORIG) 61 | len_got = len(lines_got) 62 | assert len_orig == len_expected == len_got, \ 63 | "line count differs %d %d %d" % (len_orig, len_expected, len_got) 64 | assert lines_expected == lines_got, "texts differ" 65 | 66 | 67 | test_remove_comments() 68 | -------------------------------------------------------------------------------- /tests/opcodes.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from uctypes import UINT32, BFUINT32, BF_POS, BF_LEN 9 | from esp32_ulp.opcodes import make_ins, make_ins_struct_def 10 | from esp32_ulp.opcodes import get_reg, get_imm, get_cond, arg_qualify, eval_arg, ARG, REG, IMM, SYM, COND 11 | from esp32_ulp.assemble import SymbolTable, ABS, REL, TEXT 12 | import esp32_ulp.opcodes as opcodes 13 | 14 | OPCODE_DELAY = 4 15 | LAYOUT_DELAY = """ 16 | cycles : 16 # Number of cycles to sleep 17 | unused : 12 # Unused 18 | opcode : 4 # Opcode (OPCODE_DELAY) 19 | """ 20 | 21 | 22 | def test_make_ins_struct_def(): 23 | sd = make_ins_struct_def(LAYOUT_DELAY) 24 | assert set(sd) == {'cycles', 'unused', 'opcode', 'all'} 25 | assert sd['cycles'] == BFUINT32 | 0 << BF_POS | 16 << BF_LEN 26 | assert sd['unused'] == BFUINT32 | 16 << BF_POS | 12 << BF_LEN 27 | assert sd['opcode'] == BFUINT32 | 28 << BF_POS | 4 << BF_LEN 28 | assert sd['all'] == UINT32 29 | 30 | 31 | def test_make_ins(): 32 | _delay = make_ins(LAYOUT_DELAY) 33 | _delay.cycles = 0x23 34 | _delay.unused = 0 35 | _delay.opcode = OPCODE_DELAY 36 | assert _delay.cycles == 0x23 37 | assert _delay.unused == 0 38 | assert _delay.opcode == OPCODE_DELAY 39 | assert _delay.all == 0x40000023 40 | 41 | 42 | def test_arg_qualify(): 43 | assert arg_qualify('r0') == ARG(REG, 0, 'r0') 44 | assert arg_qualify('R3') == ARG(REG, 3, 'R3') 45 | assert arg_qualify('0') == ARG(IMM, 0, '0') 46 | assert arg_qualify('-1') == ARG(IMM, -1, '-1') 47 | assert arg_qualify('1') == ARG(IMM, 1, '1') 48 | assert arg_qualify('0x20') == ARG(IMM, 32, '0x20') 49 | assert arg_qualify('0o100') == ARG(IMM, 64, '0o100') 50 | assert arg_qualify('0b1000') == ARG(IMM, 8, '0b1000') 51 | assert arg_qualify('eq') == ARG(COND, 'eq', 'eq') 52 | assert arg_qualify('Eq') == ARG(COND, 'eq', 'Eq') 53 | assert arg_qualify('EQ') == ARG(COND, 'eq', 'EQ') 54 | 55 | # for the next tests, ensure the opcodes module has a SymbolTable 56 | opcodes.symbols = SymbolTable({}, {}, {}) 57 | opcodes.symbols.set_sym('const', ABS, None, 42) # constant as defined by .set 58 | opcodes.symbols.set_sym('entry', REL, TEXT, 4) # label pointing to code 59 | 60 | assert arg_qualify('1+1') == ARG(IMM, 2, '1+1') 61 | assert arg_qualify('const >> 1') == ARG(IMM, 21, 'const >> 1') 62 | assert arg_qualify('entry') == ARG(SYM, (REL, TEXT, 4), 'entry') # symbols should not (yet) be evaluated 63 | assert arg_qualify('entry + const') == ARG(IMM, 46, 'entry + const') 64 | 65 | # clean up 66 | opcodes.symbols = None 67 | 68 | 69 | def test_get_reg(): 70 | assert get_reg('r0') == 0 71 | assert get_reg('R3') == 3 72 | 73 | 74 | def test_get_imm(): 75 | assert get_imm('42') == 42 76 | 77 | 78 | def test_get_cond(): 79 | assert get_cond('Eq') == 'eq' 80 | 81 | 82 | def test_eval_arg(): 83 | opcodes.symbols = SymbolTable({}, {}, {}) 84 | opcodes.symbols.set_sym('const', ABS, None, 42) # constant 85 | opcodes.symbols.set_sym('raise', ABS, None, 99) # constant using a python keyword as name (is allowed) 86 | 87 | assert eval_arg('1+1') == 2 88 | assert eval_arg('1+const') == 43 89 | assert eval_arg('raise*2/3') == 66 90 | assert eval_arg('raise-const') == 57 91 | assert eval_arg('(raise-const)*2') == 114 92 | assert eval_arg('const % 5') == 2 93 | assert eval_arg('const + 0x19af') == 0x19af + 42 94 | assert eval_arg('const & ~2') == 40 95 | assert eval_arg('const << 3') == 336 96 | assert eval_arg('const >> 1') == 21 97 | assert eval_arg('(const|4)&0xf') == 0xe 98 | 99 | assert_raises(ValueError, eval_arg, 'evil()') 100 | assert_raises(ValueError, eval_arg, 'def cafe()') 101 | assert_raises(ValueError, eval_arg, '1 ^ 2') 102 | assert_raises(ValueError, eval_arg, '!100') 103 | 104 | # clean up 105 | opcodes.symbols = None 106 | 107 | 108 | def assert_raises(exception, func, *args): 109 | try: 110 | func(*args) 111 | except exception: 112 | raised = True 113 | else: 114 | raised = False 115 | assert raised 116 | 117 | 118 | def test_reg_direct_ulp_addressing(): 119 | """ 120 | Test direct ULP addressing of peripheral registers 121 | input must be <= 0x3ff (10 bits) 122 | periph_sel == high 2 bits from input 123 | addr == low 8 bits from input 124 | """ 125 | 126 | ins = make_ins(""" 127 | addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC 128 | periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) 129 | unused : 8 # Unused 130 | low : 5 # Low bit 131 | high : 5 # High bit 132 | opcode : 4 # Opcode (OPCODE_RD_REG) 133 | """) 134 | 135 | ins.all = opcodes.i_reg_rd("0x0", "0", "0") 136 | assert ins.periph_sel == 0 137 | assert ins.addr == 0x0 138 | 139 | ins.all = opcodes.i_reg_rd("0x012", "0", "0") 140 | assert ins.periph_sel == 0 141 | assert ins.addr == 0x12 142 | 143 | ins.all = opcodes.i_reg_rd("0x123", "0", "0") 144 | assert ins.periph_sel == 1 145 | assert ins.addr == 0x23 146 | 147 | ins.all = opcodes.i_reg_rd("0x2ee", "0", "0") 148 | assert ins.periph_sel == 2 149 | assert ins.addr == 0xee 150 | 151 | ins.all = opcodes.i_reg_rd("0x3ff", "0", "0") 152 | assert ins.periph_sel == 3 153 | assert ins.addr == 0xff 154 | 155 | # anything bigger than 0x3ff must be a valid full address 156 | assert_raises(ValueError, opcodes.i_reg_rd, "0x400", "0", "0") 157 | 158 | 159 | def test_reg_address_translations(): 160 | """ 161 | Test addressing of peripheral registers using full DPORT bus addresses 162 | """ 163 | 164 | ins = make_ins(""" 165 | addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC 166 | periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) 167 | unused : 8 # Unused 168 | low : 5 # Low bit 169 | high : 5 # High bit 170 | opcode : 4 # Opcode (OPCODE_RD_REG) 171 | """) 172 | 173 | # direct ULP address is derived from full address as follows: 174 | # full:0x3ff484a8 == ulp:(0x3ff484a8-DR_REG_RTCCNTL_BASE) / 4 175 | # full:0x3ff484a8 == ulp:(0x3ff484a8-0x3ff48000) / 4 176 | # full:0x3ff484a8 == ulp:0x4a8 / 4 177 | # full:0x3ff484a8 == ulp:0x12a 178 | # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32.c#L149 179 | ins.all = opcodes.i_reg_rd("0x3ff484a8", "0", "0") 180 | assert ins.periph_sel == 1 # high 2 bits of 0x12a 181 | assert ins.addr == 0x2a # low 8 bits of 0x12a 182 | 183 | 184 | def test_reg_address_translations_sens(): 185 | """ 186 | Test addressing of peripheral registers using full DPORT bus addresses 187 | """ 188 | 189 | ins = make_ins(""" 190 | addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC 191 | periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) 192 | unused : 8 # Unused 193 | low : 5 # Low bit 194 | high : 5 # High bit 195 | opcode : 4 # Opcode (OPCODE_RD_REG) 196 | """) 197 | 198 | # direct ULP address is derived from full address as follows: 199 | # full:0x3ff48904 == ulp:(0x3ff48904-DR_REG_RTCCNTL_BASE) / 4 200 | # full:0x3ff48904 == ulp:(0x3ff48904-0x3f408000) / 4 201 | # full:0x3ff48904 == ulp:0x904 / 4 202 | # full:0x3ff48904 == ulp:0x241 203 | # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 204 | ins.all = opcodes.i_reg_rd("0x3ff48904", "0", "0") 205 | assert ins.periph_sel == 2 # high 2 bits of 0x241 206 | assert ins.addr == 0x41 # low 8 bits of 0x241 207 | 208 | 209 | test_make_ins_struct_def() 210 | test_make_ins() 211 | test_arg_qualify() 212 | test_get_reg() 213 | test_get_imm() 214 | test_get_cond() 215 | test_eval_arg() 216 | test_reg_direct_ulp_addressing() 217 | test_reg_address_translations() 218 | test_reg_address_translations_sens() 219 | -------------------------------------------------------------------------------- /tests/opcodes_s2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from uctypes import UINT32, BFUINT32, BF_POS, BF_LEN 9 | from esp32_ulp.opcodes_s2 import make_ins, make_ins_struct_def 10 | from esp32_ulp.opcodes_s2 import get_reg, get_imm, get_cond, arg_qualify, eval_arg, ARG, REG, IMM, SYM, COND 11 | from esp32_ulp.assemble import SymbolTable, ABS, REL, TEXT 12 | import esp32_ulp.opcodes_s2 as opcodes 13 | 14 | OPCODE_DELAY = 4 15 | LAYOUT_DELAY = """ 16 | cycles : 16 # Number of cycles to sleep 17 | unused : 12 # Unused 18 | opcode : 4 # Opcode (OPCODE_DELAY) 19 | """ 20 | 21 | 22 | def test_make_ins_struct_def(): 23 | sd = make_ins_struct_def(LAYOUT_DELAY) 24 | assert set(sd) == {'cycles', 'unused', 'opcode', 'all'} 25 | assert sd['cycles'] == BFUINT32 | 0 << BF_POS | 16 << BF_LEN 26 | assert sd['unused'] == BFUINT32 | 16 << BF_POS | 12 << BF_LEN 27 | assert sd['opcode'] == BFUINT32 | 28 << BF_POS | 4 << BF_LEN 28 | assert sd['all'] == UINT32 29 | 30 | 31 | def test_make_ins(): 32 | _delay = make_ins(LAYOUT_DELAY) 33 | _delay.cycles = 0x23 34 | _delay.unused = 0 35 | _delay.opcode = OPCODE_DELAY 36 | assert _delay.cycles == 0x23 37 | assert _delay.unused == 0 38 | assert _delay.opcode == OPCODE_DELAY 39 | assert _delay.all == 0x40000023 40 | 41 | 42 | def test_arg_qualify(): 43 | assert arg_qualify('r0') == ARG(REG, 0, 'r0') 44 | assert arg_qualify('R3') == ARG(REG, 3, 'R3') 45 | assert arg_qualify('0') == ARG(IMM, 0, '0') 46 | assert arg_qualify('-1') == ARG(IMM, -1, '-1') 47 | assert arg_qualify('1') == ARG(IMM, 1, '1') 48 | assert arg_qualify('0x20') == ARG(IMM, 32, '0x20') 49 | assert arg_qualify('0o100') == ARG(IMM, 64, '0o100') 50 | assert arg_qualify('0b1000') == ARG(IMM, 8, '0b1000') 51 | assert arg_qualify('eq') == ARG(COND, 'eq', 'eq') 52 | assert arg_qualify('Eq') == ARG(COND, 'eq', 'Eq') 53 | assert arg_qualify('EQ') == ARG(COND, 'eq', 'EQ') 54 | 55 | # for the next tests, ensure the opcodes module has a SymbolTable 56 | opcodes.symbols = SymbolTable({}, {}, {}) 57 | opcodes.symbols.set_sym('const', ABS, None, 42) # constant as defined by .set 58 | opcodes.symbols.set_sym('entry', REL, TEXT, 4) # label pointing to code 59 | 60 | assert arg_qualify('1+1') == ARG(IMM, 2, '1+1') 61 | assert arg_qualify('const >> 1') == ARG(IMM, 21, 'const >> 1') 62 | assert arg_qualify('entry') == ARG(SYM, (REL, TEXT, 4), 'entry') # symbols should not (yet) be evaluated 63 | assert arg_qualify('entry + const') == ARG(IMM, 46, 'entry + const') 64 | 65 | # clean up 66 | opcodes.symbols = None 67 | 68 | 69 | def test_get_reg(): 70 | assert get_reg('r0') == 0 71 | assert get_reg('R3') == 3 72 | 73 | 74 | def test_get_imm(): 75 | assert get_imm('42') == 42 76 | 77 | 78 | def test_get_cond(): 79 | assert get_cond('Eq') == 'eq' 80 | 81 | 82 | def test_eval_arg(): 83 | opcodes.symbols = SymbolTable({}, {}, {}) 84 | opcodes.symbols.set_sym('const', ABS, None, 42) # constant 85 | opcodes.symbols.set_sym('raise', ABS, None, 99) # constant using a python keyword as name (is allowed) 86 | 87 | assert eval_arg('1+1') == 2 88 | assert eval_arg('1+const') == 43 89 | assert eval_arg('raise*2/3') == 66 90 | assert eval_arg('raise-const') == 57 91 | assert eval_arg('(raise-const)*2') == 114 92 | assert eval_arg('const % 5') == 2 93 | assert eval_arg('const + 0x19af') == 0x19af + 42 94 | assert eval_arg('const & ~2') == 40 95 | assert eval_arg('const << 3') == 336 96 | assert eval_arg('const >> 1') == 21 97 | assert eval_arg('(const|4)&0xf') == 0xe 98 | 99 | assert_raises(ValueError, eval_arg, 'evil()') 100 | assert_raises(ValueError, eval_arg, 'def cafe()') 101 | assert_raises(ValueError, eval_arg, '1 ^ 2') 102 | assert_raises(ValueError, eval_arg, '!100') 103 | 104 | # clean up 105 | opcodes.symbols = None 106 | 107 | 108 | def assert_raises(exception, func, *args): 109 | try: 110 | func(*args) 111 | except exception: 112 | raised = True 113 | else: 114 | raised = False 115 | assert raised 116 | 117 | 118 | def test_reg_direct_ulp_addressing(): 119 | """ 120 | Test direct ULP addressing of peripheral registers 121 | input must be <= 0x3ff (10 bits) 122 | periph_sel == high 2 bits from input 123 | addr == low 8 bits from input 124 | """ 125 | 126 | ins = make_ins(""" 127 | addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC 128 | periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) 129 | unused : 8 # Unused 130 | low : 5 # Low bit 131 | high : 5 # High bit 132 | opcode : 4 # Opcode (OPCODE_RD_REG) 133 | """) 134 | 135 | ins.all = opcodes.i_reg_rd("0x0", "0", "0") 136 | assert ins.periph_sel == 0 137 | assert ins.addr == 0x0 138 | 139 | ins.all = opcodes.i_reg_rd("0x012", "0", "0") 140 | assert ins.periph_sel == 0 141 | assert ins.addr == 0x12 142 | 143 | ins.all = opcodes.i_reg_rd("0x123", "0", "0") 144 | assert ins.periph_sel == 1 145 | assert ins.addr == 0x23 146 | 147 | ins.all = opcodes.i_reg_rd("0x2ee", "0", "0") 148 | assert ins.periph_sel == 2 149 | assert ins.addr == 0xee 150 | 151 | ins.all = opcodes.i_reg_rd("0x3ff", "0", "0") 152 | assert ins.periph_sel == 3 153 | assert ins.addr == 0xff 154 | 155 | # anything bigger than 0x3ff must be a valid full address 156 | assert_raises(ValueError, opcodes.i_reg_rd, "0x400", "0", "0") 157 | 158 | 159 | def test_reg_address_translations_s2(): 160 | """ 161 | Test addressing of ESP32-S2 peripheral registers using full DPORT bus addresses 162 | """ 163 | 164 | ins = make_ins(""" 165 | addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC 166 | periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) 167 | unused : 8 # Unused 168 | low : 5 # Low bit 169 | high : 5 # High bit 170 | opcode : 4 # Opcode (OPCODE_RD_REG) 171 | """) 172 | 173 | # direct ULP address is derived from full address as follows: 174 | # full:0x3f4084a8 == ulp:(0x3f4084a8-DR_REG_RTCCNTL_BASE) / 4 175 | # full:0x3f4084a8 == ulp:(0x3f4084a8-0x3f408000) / 4 176 | # full:0x3f4084a8 == ulp:0x4a8 / 4 177 | # full:0x3f4084a8 == ulp:0x12a 178 | # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 179 | ins.all = opcodes.i_reg_rd("0x3f4084a8", "0", "0") 180 | assert ins.periph_sel == 1 # high 2 bits of 0x12a 181 | assert ins.addr == 0x2a # low 8 bits of 0x12a 182 | 183 | 184 | def test_reg_address_translations_s2_sens(): 185 | """ 186 | Test addressing of ESP32-S2 peripheral registers using full DPORT bus addresses 187 | """ 188 | 189 | ins = make_ins(""" 190 | addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC 191 | periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) 192 | unused : 8 # Unused 193 | low : 5 # Low bit 194 | high : 5 # High bit 195 | opcode : 4 # Opcode (OPCODE_RD_REG) 196 | """) 197 | 198 | # direct ULP address is derived from full address as follows: 199 | # full:0x3f408904 == ulp:(0x3f408904-DR_REG_RTCCNTL_BASE) / 4 200 | # full:0x3f408904 == ulp:(0x3f408904-0x3f408000) / 4 201 | # full:0x3f408904 == ulp:0x904 / 4 202 | # full:0x3f408904 == ulp:0x241 203 | # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 204 | ins.all = opcodes.i_reg_rd("0x3f408904", "0", "0") 205 | assert ins.periph_sel == 2 # high 2 bits of 0x241 206 | assert ins.addr == 0x41 # low 8 bits of 0x241 207 | 208 | 209 | def test_reg_address_translations_s3(): 210 | """ 211 | Test addressing of ESP32-S3 peripheral registers using full DPORT bus addresses 212 | """ 213 | 214 | ins = make_ins(""" 215 | addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC 216 | periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) 217 | unused : 8 # Unused 218 | low : 5 # Low bit 219 | high : 5 # High bit 220 | opcode : 4 # Opcode (OPCODE_RD_REG) 221 | """) 222 | 223 | # direct ULP address is derived from full address as follows: 224 | # full:0x600084a8 == ulp:(0x600084a8-DR_REG_RTCCNTL_BASE) / 4 225 | # full:0x600084a8 == ulp:(0x600084a8-0x60008000) / 4 226 | # full:0x600084a8 == ulp:0x4a8 / 4 227 | # full:0x600084a8 == ulp:0x12a 228 | # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 229 | ins.all = opcodes.i_reg_rd("0x600084a8", "0", "0") 230 | assert ins.periph_sel == 1 # high 2 bits of 0x12a 231 | assert ins.addr == 0x2a # low 8 bits of 0x12a 232 | 233 | 234 | def test_reg_address_translations_s3_sens(): 235 | """ 236 | Test addressing of ESP32-S3 peripheral registers using full DPORT bus addresses 237 | """ 238 | 239 | ins = make_ins(""" 240 | addr : 8 # Address within either RTC_CNTL, RTC_IO, or SARADC 241 | periph_sel : 2 # Select peripheral: RTC_CNTL (0), RTC_IO(1), SARADC(2) 242 | unused : 8 # Unused 243 | low : 5 # Low bit 244 | high : 5 # High bit 245 | opcode : 4 # Opcode (OPCODE_RD_REG) 246 | """) 247 | 248 | # direct ULP address is derived from full address as follows: 249 | # full:0x60008904 == ulp:(0x60008904-DR_REG_RTCCNTL_BASE) / 4 250 | # full:0x60008904 == ulp:(0x60008904-0x60008000) / 4 251 | # full:0x60008904 == ulp:0x904 / 4 252 | # full:0x60008904 == ulp:0x241 253 | # see: https://github.com/espressif/binutils-esp32ulp/blob/249ec34/gas/config/tc-esp32ulp_esp32s2.c#L78 254 | ins.all = opcodes.i_reg_rd("0x60008904", "0", "0") 255 | assert ins.periph_sel == 2 # high 2 bits of 0x241 256 | assert ins.addr == 0x41 # low 8 bits of 0x241 257 | 258 | 259 | test_make_ins_struct_def() 260 | test_make_ins() 261 | test_arg_qualify() 262 | test_get_reg() 263 | test_get_imm() 264 | test_get_cond() 265 | test_eval_arg() 266 | test_reg_direct_ulp_addressing() 267 | test_reg_address_translations_s2() 268 | test_reg_address_translations_s3() 269 | test_reg_address_translations_s2_sens() 270 | test_reg_address_translations_s3_sens() 271 | -------------------------------------------------------------------------------- /tests/preprocess.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | import os 9 | 10 | from esp32_ulp.preprocess import Preprocessor 11 | from esp32_ulp.definesdb import DefinesDB, DBNAME 12 | from esp32_ulp.util import file_exists 13 | 14 | tests = [] 15 | 16 | 17 | def test(param): 18 | tests.append(param) 19 | 20 | 21 | def resolve_relative_path(filename): 22 | """ 23 | Returns the full path to the filename provided, taken relative to the current file 24 | e.g. 25 | if this file was file.py at /path/to/file.py 26 | and the provided relative filename was tests/unit.py 27 | then the resulting path would be /path/to/tests/unit.py 28 | """ 29 | r = __file__.rsplit("/", 1) # poor man's os.path.dirname(__file__) 30 | head = r[0] 31 | if len(r) == 1 or not head: 32 | return filename 33 | return "%s/%s" % (head, filename) 34 | 35 | 36 | @test 37 | def test_replace_defines_should_return_empty_line_given_empty_string(): 38 | p = Preprocessor() 39 | 40 | assert p.preprocess("") == "" 41 | 42 | 43 | @test 44 | def replace_defines_should_return_remove_comments(): 45 | p = Preprocessor() 46 | 47 | line = "// some comment" 48 | expected = "" 49 | assert p.preprocess(line) == expected 50 | 51 | 52 | @test 53 | def test_parse_defines(): 54 | p = Preprocessor() 55 | 56 | assert p.parse_define_line("") == {} 57 | assert p.parse_define_line("// comment") == {} 58 | assert p.parse_define_line(" // comment") == {} 59 | assert p.parse_define_line(" /* comment */") == {} 60 | assert p.parse_define_line(" /* comment */ #define A 42") == {} # #define must be the first thing on a line 61 | assert p.parse_define_line("#define a 1") == {"a": "1"} 62 | assert p.parse_define_line(" #define a 1") == {"a": "1"} 63 | assert p.parse_define_line("#define a 1 2") == {"a": "1 2"} 64 | assert p.parse_define_line("#define f(a,b) 1") == {} # macros not supported 65 | assert p.parse_define_line("#define f(a, b) 1") == {} # macros not supported 66 | assert p.parse_define_line("#define f (a,b) 1") == {"f": "(a,b) 1"} # f is not a macro 67 | assert p.parse_define_line("#define f (a, b) 1") == {"f": "(a, b) 1"} # f is not a macro 68 | assert p.parse_define_line("#define RTC_ADDR 0x12345 // start of range") == {"RTC_ADDR": "0x12345"} 69 | 70 | 71 | @test 72 | def test_parse_defines_handles_multiple_input_lines(): 73 | p = Preprocessor() 74 | 75 | multi_line_1 = """\ 76 | #define ID_WITH_UNDERSCORE something 77 | #define ID2 somethingelse 78 | """ 79 | assert p.parse_defines(multi_line_1) == {"ID_WITH_UNDERSCORE": "something", "ID2": "somethingelse"} 80 | 81 | 82 | @test 83 | def test_parse_defines_does_not_understand_comments_by_current_design(): 84 | # comments are not understood. lines are expected to already have comments removed! 85 | p = Preprocessor() 86 | 87 | multi_line_2 = """\ 88 | #define ID_WITH_UNDERSCORE something 89 | /* 90 | #define ID2 somethingelse 91 | */ 92 | """ 93 | assert "ID2" in p.parse_defines(multi_line_2) 94 | 95 | 96 | @test 97 | def test_parse_defines_does_not_understand_line_continuations_with_backslash_by_current_design(): 98 | p = Preprocessor() 99 | 100 | multi_line_3 = r""" 101 | #define ID_WITH_UNDERSCORE something \ 102 | line2 103 | """ 104 | 105 | assert p.parse_defines(multi_line_3) == {"ID_WITH_UNDERSCORE": "something \\"} 106 | 107 | 108 | @test 109 | def preprocess_should_remove_comments_and_defines_but_keep_the_lines_as_empty_lines(): 110 | p = Preprocessor() 111 | 112 | lines = """\ 113 | // copyright 114 | #define A 1 115 | 116 | move r1, r2""" 117 | 118 | assert p.preprocess(lines) == "\n\n\n\tmove r1, r2" 119 | 120 | 121 | @test 122 | def preprocess_should_replace_words_defined(): 123 | p = Preprocessor() 124 | 125 | lines = """\ 126 | #define DR_REG_RTCIO_BASE 0x3ff48400 127 | 128 | move r1, DR_REG_RTCIO_BASE""" 129 | 130 | assert "move r1, 0x3ff48400" in p.preprocess(lines) 131 | 132 | 133 | @test 134 | def preprocess_should_replace_words_defined_multiple_times(): 135 | p = Preprocessor() 136 | 137 | lines = """\ 138 | #define DR_REG_RTCIO_BASE 0x3ff48400 139 | 140 | move r1, DR_REG_RTCIO_BASE #once 141 | move r2, DR_REG_RTCIO_BASE #second time""" 142 | 143 | assert "move r1, 0x3ff48400" in p.preprocess(lines) 144 | assert "move r2, 0x3ff48400" in p.preprocess(lines) 145 | 146 | 147 | @test 148 | def preprocess_should_replace_all_defined_words(): 149 | p = Preprocessor() 150 | 151 | lines = """\ 152 | #define DR_REG_RTCIO_BASE 0x3ff48400 153 | #define SOME_OFFSET 4 154 | 155 | move r1, DR_REG_RTCIO_BASE 156 | add r2, r1, SOME_OFFSET""" 157 | 158 | assert "move r1, 0x3ff48400" in p.preprocess(lines) 159 | assert "add r2, r1, 4" in p.preprocess(lines) 160 | 161 | 162 | @test 163 | def preprocess_should_not_replace_substrings_within_identifiers(): 164 | p = Preprocessor() 165 | 166 | # ie. if AAA is defined don't touch PREFIX_AAA_SUFFIX 167 | lines = """\ 168 | #define RTCIO 4 169 | move r1, DR_REG_RTCIO_BASE""" 170 | 171 | assert "DR_REG_4_BASE" not in p.preprocess(lines) 172 | 173 | # ie. if A and AA are defined, don't replace AA as two A's but with AA 174 | lines = """\ 175 | #define A 4 176 | #define AA 8 177 | move r1, A 178 | move r2, AA""" 179 | 180 | assert "move r1, 4" in p.preprocess(lines) 181 | assert "move r2, 8" in p.preprocess(lines) 182 | 183 | 184 | @test 185 | def preprocess_should_replace_defines_used_in_defines(): 186 | p = Preprocessor() 187 | 188 | lines = """\ 189 | #define BITS (BASE << 4) 190 | #define BASE 0x1234 191 | 192 | move r1, BITS 193 | move r2, BASE""" 194 | 195 | assert "move r1, (0x1234 << 4)" in p.preprocess(lines) 196 | 197 | 198 | @test 199 | def test_expand_rtc_macros(): 200 | p = Preprocessor() 201 | 202 | assert p.expand_rtc_macros("") == "" 203 | assert p.expand_rtc_macros("abc") == "abc" 204 | assert p.expand_rtc_macros("WRITE_RTC_REG(1, 2, 3, 4)") == "\treg_wr 1, 2 + 3 - 1, 2, 4" 205 | assert p.expand_rtc_macros("READ_RTC_REG(1, 2, 3)") == "\treg_rd 1, 2 + 3 - 1, 2" 206 | assert p.expand_rtc_macros("WRITE_RTC_FIELD(1, 2, 3)") == "\treg_wr 1, 2 + 1 - 1, 2, 3 & 1" 207 | assert p.expand_rtc_macros("READ_RTC_FIELD(1, 2)") == "\treg_rd 1, 2 + 1 - 1, 2" 208 | 209 | 210 | @test 211 | def preprocess_should_replace_BIT_with_empty_string_unless_defined(): 212 | # by default replace BIT with empty string (see description for why in the code) 213 | src = " move r1, 0x123 << BIT(24)" 214 | assert "move r1, 0x123 << (24)" in Preprocessor().preprocess(src) 215 | 216 | # but if BIT is defined, use that 217 | src = """\ 218 | #define BIT 12 219 | 220 | move r1, BIT""" 221 | 222 | assert "move r1, 12" in Preprocessor().preprocess(src) 223 | 224 | 225 | @test 226 | def test_process_include_file(): 227 | p = Preprocessor() 228 | 229 | defines = p.process_include_file(resolve_relative_path('fixtures/incl.h')) 230 | 231 | assert defines['CONST1'] == '42' 232 | assert defines['CONST2'] == '99' 233 | assert defines.get('MULTI_LINE', None) == 'abc \\' # correct. line continuations not supported 234 | assert 'MACRO' not in defines 235 | 236 | 237 | @test 238 | def test_process_include_file_with_multiple_files(): 239 | p = Preprocessor() 240 | 241 | defines = p.process_include_file(resolve_relative_path('fixtures/incl.h')) 242 | defines = p.process_include_file(resolve_relative_path('fixtures/incl2.h')) 243 | 244 | assert defines['CONST1'] == '42', "constant from incl.h" 245 | assert defines['CONST2'] == '123', "constant overridden by incl2.h" 246 | assert defines['CONST3'] == '777', "constant from incl2.h" 247 | 248 | 249 | @test 250 | def test_process_include_file_using_database(): 251 | db = DefinesDB() 252 | db.clear() 253 | 254 | p = Preprocessor() 255 | p.use_db(db) 256 | 257 | p.process_include_file(resolve_relative_path('fixtures/incl.h')) 258 | p.process_include_file(resolve_relative_path('fixtures/incl2.h')) 259 | 260 | assert db['CONST1'] == '42', "constant from incl.h" 261 | assert db['CONST2'] == '123', "constant overridden by incl2.h" 262 | assert db['CONST3'] == '777', "constant from incl2.h" 263 | 264 | db.close() 265 | 266 | 267 | @test 268 | def test_process_include_file_should_not_load_database_keys_into_instance_defines_dictionary(): 269 | db = DefinesDB() 270 | db.clear() 271 | 272 | p = Preprocessor() 273 | p.use_db(db) 274 | 275 | p.process_include_file(resolve_relative_path('fixtures/incl.h')) 276 | 277 | # a bit hackish to reference instance-internal state 278 | # but it's important to verify this, as we otherwise run out of memory on device 279 | assert 'CONST2' not in p._defines 280 | 281 | 282 | 283 | @test 284 | def test_preprocess_should_use_definesdb_when_provided(): 285 | p = Preprocessor() 286 | 287 | content = """\ 288 | #define LOCALCONST 42 289 | 290 | entry: 291 | move r1, LOCALCONST 292 | move r2, DBKEY 293 | """ 294 | 295 | # first try without db 296 | result = p.preprocess(content) 297 | 298 | assert "move r1, 42" in result 299 | assert "move r2, DBKEY" in result 300 | assert "move r2, 99" not in result 301 | 302 | # now try with db 303 | db = DefinesDB() 304 | db.clear() 305 | db.update({'DBKEY': '99'}) 306 | p.use_db(db) 307 | 308 | result = p.preprocess(content) 309 | 310 | assert "move r1, 42" in result 311 | assert "move r2, 99" in result 312 | assert "move r2, DBKEY" not in result 313 | 314 | 315 | @test 316 | def test_preprocess_should_ensure_no_definesdb_is_created_when_only_reading_from_it(): 317 | content = """\ 318 | #define CONST 42 319 | move r1, CONST""" 320 | 321 | # remove any existing db 322 | db = DefinesDB() 323 | db.clear() 324 | assert not file_exists(DBNAME) 325 | 326 | # now preprocess using db 327 | p = Preprocessor() 328 | p.use_db(db) 329 | 330 | result = p.preprocess(content) 331 | 332 | assert "move r1, 42" in result 333 | 334 | assert not file_exists(DBNAME) 335 | 336 | 337 | @test 338 | def test_preprocess_should_ensure_the_definesdb_is_properly_closed_after_use(): 339 | content = """\ 340 | #define CONST 42 341 | move r1, CONST""" 342 | 343 | # remove any existing db 344 | db = DefinesDB() 345 | db.open() 346 | assert db.is_open() 347 | 348 | # now preprocess using db 349 | p = Preprocessor() 350 | p.use_db(db) 351 | 352 | p.preprocess(content) 353 | 354 | assert not db.is_open() 355 | 356 | 357 | if __name__ == '__main__': 358 | # run all methods marked with @test 359 | for t in tests: 360 | t() 361 | -------------------------------------------------------------------------------- /tests/tools: -------------------------------------------------------------------------------- 1 | ../tools -------------------------------------------------------------------------------- /tests/util.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | import os 9 | from esp32_ulp.util import split_tokens, validate_expression, file_exists 10 | 11 | tests = [] 12 | 13 | 14 | def test(param): 15 | """ 16 | the @test decorator 17 | """ 18 | tests.append(param) 19 | 20 | 21 | @test 22 | def test_split_tokens(): 23 | assert split_tokens("") == [] 24 | assert split_tokens("t") == ['t'] 25 | assert split_tokens("test") == ['test'] 26 | assert split_tokens("t t") == ['t', ' ', 't'] 27 | assert split_tokens("t,t") == ['t', ',', 't'] 28 | assert split_tokens("test(arg)") == ['test', '(', 'arg', ')'] 29 | assert split_tokens("test(arg,arg2)") == ['test', '(', 'arg', ',', 'arg2', ')'] 30 | assert split_tokens("test(arg,arg2)") == ['test', '(', 'arg', ',', 'arg2', ')'] 31 | assert split_tokens(" test( arg, arg2)") == [' ', 'test', '(', ' ', 'arg', ',', ' ', 'arg2', ')'] 32 | assert split_tokens(" test( arg ) ") == [' ', 'test', '(', ' ', 'arg', ' ', ')', ' '] 33 | assert split_tokens("\t test \t ") == ['\t ', 'test', " \t "] 34 | assert split_tokens("test\nrow2") == ['test', "\n", "row2"] 35 | 36 | # split_token does not support comments. should generally only be used after comments are already stripped 37 | assert split_tokens("test(arg /*comment*/)") == ['test', '(', 'arg', ' ', '/', '*', 'comment', '*', '/', ')'] 38 | assert split_tokens("#test") == ['#', 'test'] 39 | 40 | 41 | @test 42 | def test_validate_expression(): 43 | assert validate_expression('') is True 44 | assert validate_expression('1') is True 45 | assert validate_expression('1+1') is True 46 | assert validate_expression('(1+1)') is True 47 | assert validate_expression('(1+1)*2') is True 48 | assert validate_expression('(1 + 1)') is True 49 | assert validate_expression('10 % 2') is True 50 | assert validate_expression('0x100 << 2') is True 51 | assert validate_expression('0x100 & ~2') is True 52 | assert validate_expression('0xabcdef') is True 53 | assert validate_expression('0x123def') is True 54 | assert validate_expression('0xABC') is True 55 | assert validate_expression('0xaBc') is True 56 | assert validate_expression('0Xabc') is True 57 | assert validate_expression('0X123ABC') is True 58 | assert validate_expression('2*3+4/5&6|7') is True 59 | assert validate_expression('(((((1+1) * 2') is True # valid characters, even if expression is not valid 60 | 61 | assert validate_expression(':') is False 62 | assert validate_expression('_') is False 63 | assert validate_expression('=') is False 64 | assert validate_expression('.') is False 65 | assert validate_expression('!') is False 66 | assert validate_expression('123 ^ 4') is False # operator not supported for now 67 | assert validate_expression('evil()') is False 68 | assert validate_expression('def cafe()') is False # valid hex digits, but potentially dangerous code 69 | assert validate_expression('def CAFE()') is False 70 | 71 | 72 | @test 73 | def test_file_exists(): 74 | testfile = '.testfile' 75 | with open(testfile, 'w') as f: 76 | f.write('contents') 77 | 78 | assert file_exists(testfile) 79 | 80 | os.remove(testfile) 81 | 82 | assert not file_exists(testfile) 83 | 84 | 85 | if __name__ == '__main__': 86 | # run all methods marked with @test 87 | for t in tests: 88 | t() 89 | -------------------------------------------------------------------------------- /tools/decode.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | import esp32_ulp.opcodes as opcodes 9 | 10 | 11 | alu_cnt_ops = ('STAGE_INC', 'STAGE_DEC', 'STAGE_RST') 12 | alu_ops = ('ADD', 'SUB', 'AND', 'OR', 'MOVE', 'LSH', 'RSH') 13 | jump_types = ('--', 'EQ', 'OV') 14 | cmp_ops = ('LT', 'GE', 'LE', 'EQ', 'GT') 15 | 16 | lookup = { 17 | opcodes.OPCODE_ADC: ('ADC', opcodes._adc, lambda op: 'ADC r%s, %s, %s' % (op.dreg, op.mux, op.sar_sel)), 18 | opcodes.OPCODE_ALU: ('ALU', opcodes._alu_imm, { 19 | opcodes.SUB_OPCODE_ALU_CNT: ( 20 | 'ALU_CNT', 21 | opcodes._alu_cnt, 22 | lambda op: '%s%s' % (alu_cnt_ops[op.sel], '' if op.sel == opcodes.ALU_SEL_RST else ' %s' % op.imm) 23 | ), 24 | opcodes.SUB_OPCODE_ALU_IMM: ( 25 | 'ALU_IMM', 26 | opcodes._alu_imm, 27 | lambda op: '%s r%s, %s' % (alu_ops[op.sel], op.dreg, op.imm) if op.sel == opcodes.ALU_SEL_MOV 28 | else '%s r%s, r%s, %s' % (alu_ops[op.sel], op.dreg, op.sreg, op.imm) 29 | ), 30 | opcodes.SUB_OPCODE_ALU_REG: ( 31 | 'ALU_REG', 32 | opcodes._alu_reg, 33 | lambda op: '%s r%s, r%s' % (alu_ops[op.sel], op.dreg, op.sreg) if op.sel == opcodes.ALU_SEL_MOV 34 | else '%s r%s, r%s, r%s' % (alu_ops[op.sel], op.dreg, op.sreg, op.treg) 35 | ), 36 | }), 37 | opcodes.OPCODE_BRANCH: ('BRANCH', opcodes._bx, { 38 | opcodes.SUB_OPCODE_BX: ( 39 | 'BX', 40 | opcodes._bx, 41 | lambda op: 'JUMP %s%s' % (op.addr if op.reg == 0 else 'r%s' % op.dreg, ', %s' % jump_types[op.type] 42 | if op.type != 0 else '') 43 | ), 44 | opcodes.SUB_OPCODE_BR: ( 45 | 'BR', 46 | opcodes._br, 47 | lambda op: 'JUMPR %s, %s, %s' % ('%s%s' % ('-' if op.sign == 1 else '', op.offset), op.imm, cmp_ops[op.cmp]) 48 | ), 49 | opcodes.SUB_OPCODE_BS: ( 50 | 'BS', 51 | opcodes._bs, 52 | lambda op: 'JUMPS %s, %s, %s' % ('%s%s' % ('-' if op.sign == 1 else '', op.offset), op.imm, cmp_ops[op.cmp]) 53 | ), 54 | }), 55 | opcodes.OPCODE_DELAY: ( 56 | 'DELAY', 57 | opcodes._delay, 58 | lambda op: 'NOP' if op.cycles == 0 else 'WAIT %s' % op.cycles 59 | ), 60 | opcodes.OPCODE_END: ('END', opcodes._end, { 61 | opcodes.SUB_OPCODE_END: ( 62 | 'WAKE', 63 | opcodes._end 64 | ), 65 | opcodes.SUB_OPCODE_SLEEP: ( 66 | 'SLEEP', 67 | opcodes._sleep, 68 | lambda op: 'SLEEP %s' % op.cycle_sel 69 | ), 70 | }), 71 | opcodes.OPCODE_HALT: ('HALT', opcodes._halt), 72 | opcodes.OPCODE_I2C: ( 73 | 'I2C', 74 | opcodes._i2c, 75 | lambda op: 'I2C_%s %s, %s, %s, %s' % ('RD' if op.rw == 0 else 'WR', op.sub_addr, op.high, op.low, op.i2c_sel) 76 | ), 77 | opcodes.OPCODE_LD: ('LD', opcodes._ld, lambda op: 'LD r%s, r%s, %s' % (op.dreg, op.sreg, op.offset)), 78 | opcodes.OPCODE_ST: ('ST', opcodes._st, lambda op: 'ST r%s, r%s, %s' % (op.sreg, op.dreg, op.offset)), 79 | opcodes.OPCODE_RD_REG: ( 80 | 'RD_REG', 81 | opcodes._rd_reg, 82 | lambda op: 'REG_RD 0x%x, %s, %s' % (op.periph_sel << 8 | op.addr, op.high, op.low) 83 | ), 84 | opcodes.OPCODE_WR_REG: ( 85 | 'WR_REG', 86 | opcodes._wr_reg, 87 | lambda op: 'REG_WR 0x%x, %s, %s, %s' % (op.periph_sel << 8 | op.addr, op.high, op.low, op.data) 88 | ), 89 | opcodes.OPCODE_TSENS: ('TSENS', opcodes._tsens, lambda op: 'TSENS r%s, %s' % (op.dreg, op.delay)), 90 | } 91 | 92 | 93 | def decode_instruction(i): 94 | if i == 0: 95 | raise Exception('') 96 | 97 | ins = opcodes._end 98 | ins.all = i # abuse a struct to get opcode 99 | 100 | params = lookup.get(ins.opcode, None) 101 | 102 | if not params: 103 | raise Exception('Unknown instruction') 104 | 105 | if len(params) == 3: 106 | name, ins, third = params 107 | ins.all = i 108 | 109 | if callable(third): 110 | params = (third(ins), ins) 111 | else: 112 | params = third.get(ins.sub_opcode, ()) 113 | 114 | if len(params) == 3: 115 | name, ins, pretty = params 116 | ins.all = i 117 | name = pretty(ins) 118 | else: 119 | name, ins = params 120 | ins.all = i 121 | 122 | return ins, name 123 | 124 | 125 | def get_instruction_fields(ins): 126 | possible_fields = ( 127 | 'addr', 'cmp', 'cycle_sel', 'cycles', 'data', 'delay', 'dreg', 128 | 'high', 'i2c_sel', 'imm', 'low', 'mux', 'offset', 'opcode', 129 | 'periph_sel', 'reg', 'rw', 'sar_sel', 'sel', 'sign', 'sreg', 130 | 'sub_addr', 'sub_opcode', 'treg', 'type', 'unused', 'unused1', 131 | 'unused2', 'wakeup' 132 | ) 133 | field_details = [] 134 | for field in possible_fields: 135 | extra = '' 136 | try: 137 | # eval is ugly but constrained to possible_fields and variable ins 138 | val = eval('i.%s' % field, {}, {'i': ins}) 139 | if (val>9): 140 | extra = ' (0x%02x)' % val 141 | except KeyError: 142 | continue 143 | 144 | if field == 'sel': # ALU 145 | if ins.sub_opcode == opcodes.SUB_OPCODE_ALU_CNT: 146 | extra = ' (%s)' % alu_cnt_ops[val] 147 | else: 148 | extra = ' (%s)' % alu_ops[val] 149 | elif field == 'type': # JUMP 150 | extra = ' (%s)' % jump_types[val] 151 | elif field == 'cmp': # JUMPR/JUMPS 152 | extra = ' (%s)' % cmp_ops[val] 153 | 154 | field_details.append((field, val, extra)) 155 | 156 | return field_details 157 | -------------------------------------------------------------------------------- /tools/decode_s2.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | import esp32_ulp.opcodes_s2 as opcodes 9 | 10 | 11 | alu_cnt_ops = ('STAGE_INC', 'STAGE_DEC', 'STAGE_RST') 12 | alu_ops = ('ADD', 'SUB', 'AND', 'OR', 'MOVE', 'LSH', 'RSH') 13 | jump_types = ('--', 'EQ', 'OV') 14 | cmp_ops = ('LT', 'GT', 'EQ') 15 | bs_cmp_ops = ('??', 'LT', '??', 'GT', 'EQ', 'LE', '??', 'GE') 16 | 17 | lookup = { 18 | opcodes.OPCODE_ADC: ('ADC', opcodes._adc, lambda op: 'ADC r%s, %s, %s' % (op.dreg, op.mux, op.sar_sel)), 19 | opcodes.OPCODE_ALU: ('ALU', opcodes._alu_imm, { 20 | opcodes.SUB_OPCODE_ALU_CNT: ( 21 | 'ALU_CNT', 22 | opcodes._alu_cnt, 23 | lambda op: '%s%s' % (alu_cnt_ops[op.sel], '' if op.sel == opcodes.ALU_SEL_STAGE_RST else ' %s' % op.imm) 24 | ), 25 | opcodes.SUB_OPCODE_ALU_IMM: ( 26 | 'ALU_IMM', 27 | opcodes._alu_imm, 28 | lambda op: '%s r%s, %s' % (alu_ops[op.sel], op.dreg, op.imm) if op.sel == opcodes.ALU_SEL_MOV 29 | else '%s r%s, r%s, %s' % (alu_ops[op.sel], op.dreg, op.sreg, op.imm) 30 | ), 31 | opcodes.SUB_OPCODE_ALU_REG: ( 32 | 'ALU_REG', 33 | opcodes._alu_reg, 34 | lambda op: '%s r%s, r%s' % (alu_ops[op.sel], op.dreg, op.sreg) if op.sel == opcodes.ALU_SEL_MOV 35 | else '%s r%s, r%s, r%s' % (alu_ops[op.sel], op.dreg, op.sreg, op.treg) 36 | ), 37 | }), 38 | opcodes.OPCODE_BRANCH: ('BRANCH', opcodes._bx, { 39 | opcodes.SUB_OPCODE_BX: ( 40 | 'BX', 41 | opcodes._bx, 42 | lambda op: 'JUMP %s%s' % (op.addr if op.reg == 0 else 'r%s' % op.dreg, ', %s' % jump_types[op.type] 43 | if op.type != 0 else '') 44 | ), 45 | opcodes.SUB_OPCODE_B: ( 46 | 'BR', 47 | opcodes._b, 48 | lambda op: 'JUMPR %s, %s, %s' % ('%s%s' % ('-' if op.sign == 1 else '', op.offset), op.imm, cmp_ops[op.cmp]) 49 | ), 50 | opcodes.SUB_OPCODE_BS: ( 51 | 'BS', 52 | opcodes._bs, 53 | lambda op: 'JUMPS %s, %s, %s' % ('%s%s' % ('-' if op.sign == 1 else '', op.offset), op.imm, bs_cmp_ops[op.cmp]) 54 | ), 55 | }), 56 | opcodes.OPCODE_DELAY: ( 57 | 'DELAY', 58 | opcodes._delay, 59 | lambda op: 'NOP' if op.cycles == 0 else 'WAIT %s' % op.cycles 60 | ), 61 | opcodes.OPCODE_END: ('END', opcodes._end, { 62 | opcodes.SUB_OPCODE_END: ( 63 | 'WAKE', 64 | opcodes._end 65 | ), 66 | }), 67 | opcodes.OPCODE_HALT: ('HALT', opcodes._halt), 68 | opcodes.OPCODE_I2C: ( 69 | 'I2C', 70 | opcodes._i2c, 71 | lambda op: 'I2C_%s %s, %s, %s, %s' % ('RD' if op.rw == 0 else 'WR', op.sub_addr, op.high, op.low, op.i2c_sel) 72 | ), 73 | opcodes.OPCODE_LD: ( 74 | 'LD/LDH', 75 | opcodes._ld, 76 | lambda op: '%s r%s, r%s, %s' % ('LDH' if op.rd_upper else 'LD', op.dreg, op.sreg, twos_comp(op.offset, 11)) 77 | ), 78 | opcodes.OPCODE_ST: ('ST', opcodes._st, { 79 | opcodes.SUB_OPCODE_ST_AUTO: ( 80 | 'STI/STI32', 81 | opcodes._st, 82 | lambda op: 'STI32 r%s, r%s, %s' % (op.sreg, op.dreg, op.label) if op.wr_way == 0 83 | else 'STI r%s, r%s, %s' % (op.sreg, op.dreg, op.label) if op.label 84 | else 'STI r%s, r%s' % (op.sreg, op.dreg) 85 | ), 86 | opcodes.SUB_OPCODE_ST_OFFSET: ( 87 | 'STO', 88 | opcodes._st, 89 | lambda op: 'STO %s' % twos_comp(op.offset, 11) 90 | ), 91 | opcodes.SUB_OPCODE_ST: ( 92 | 'ST/STH/ST32', 93 | opcodes._st, 94 | lambda op: '%s r%s, r%s, %s, %s' % ('STH' if op.upper else 'STL', op.sreg, op.dreg, twos_comp(op.offset, 11), op.label) if op.wr_way and op.label 95 | else '%s r%s, r%s, %s' % ('STH' if op.upper else 'ST', op.sreg, op.dreg, twos_comp(op.offset, 11)) if op.wr_way 96 | else 'ST32 r%s, r%s, %s, %s' % (op.sreg, op.dreg, twos_comp(op.offset, 11), op.label) 97 | ) 98 | }), 99 | opcodes.OPCODE_RD_REG: ( 100 | 'RD_REG', 101 | opcodes._rd_reg, 102 | lambda op: 'REG_RD 0x%x, %s, %s' % (op.periph_sel << 8 | op.addr, op.high, op.low) 103 | ), 104 | opcodes.OPCODE_WR_REG: ( 105 | 'WR_REG', 106 | opcodes._wr_reg, 107 | lambda op: 'REG_WR 0x%x, %s, %s, %s' % (op.periph_sel << 8 | op.addr, op.high, op.low, op.data) 108 | ), 109 | opcodes.OPCODE_TSENS: ('TSENS', opcodes._tsens, lambda op: 'TSENS r%s, %s' % (op.dreg, op.delay)), 110 | } 111 | 112 | 113 | def twos_comp(val, bits): 114 | """ 115 | compute the correct value of a 2's complement 116 | based on the number of bits in the source 117 | """ 118 | if (val & (1 << (bits - 1))) != 0: # if sign bit is set e.g., 8bit: 128-255 119 | val = val - (1 << bits) # compute negative value 120 | return val 121 | 122 | 123 | def decode_instruction(i): 124 | if i == 0: 125 | raise Exception('') 126 | 127 | ins = opcodes._end 128 | ins.all = i # abuse a struct to get opcode 129 | 130 | params = lookup.get(ins.opcode, None) 131 | 132 | if not params: 133 | raise Exception('Unknown instruction') 134 | 135 | if len(params) == 3: 136 | name, ins, third = params 137 | ins.all = i 138 | 139 | if callable(third): 140 | params = (third(ins), ins) 141 | else: 142 | params = third.get(ins.sub_opcode, ()) 143 | 144 | if len(params) == 3: 145 | name, ins, pretty = params 146 | ins.all = i 147 | name = pretty(ins) 148 | else: 149 | name, ins = params 150 | ins.all = i 151 | 152 | return ins, name 153 | 154 | 155 | def get_instruction_fields(ins): 156 | possible_fields = ( 157 | 'addr', 'cmp', 'cycle_sel', 'cycles', 'data', 'delay', 'dreg', 158 | 'high', 'i2c_sel', 'imm', 'low', 'mux', 'offset', 'opcode', 159 | 'periph_sel', 'reg', 'rw', 'sar_sel', 'sel', 'sign', 'sreg', 160 | 'sub_addr', 'sub_opcode', 'treg', 'type', 'unused', 'unused1', 161 | 'unused2', 'wakeup', 162 | 'rd_upper', 'label', 'upper', 'wr_way', 163 | ) 164 | field_details = [] 165 | for field in possible_fields: 166 | extra = '' 167 | try: 168 | # eval is ugly but constrained to possible_fields and variable ins 169 | val = eval('i.%s' % field, {}, {'i': ins}) 170 | if (val>9): 171 | extra = ' (0x%02x)' % val 172 | except KeyError: 173 | continue 174 | 175 | if field == 'sel': # ALU 176 | if ins.sub_opcode == opcodes.SUB_OPCODE_ALU_CNT: 177 | extra = ' (%s)' % alu_cnt_ops[val] 178 | else: 179 | extra = ' (%s)' % alu_ops[val] 180 | elif field == 'type': # JUMP 181 | extra = ' (%s)' % jump_types[val] 182 | elif field == 'cmp': # JUMPR/JUMPS 183 | if ins.sub_opcode == opcodes.SUB_OPCODE_BS: 184 | extra = ' (%s)' % bs_cmp_ops[val] 185 | else: 186 | extra = ' (%s)' % cmp_ops[val] 187 | elif field == 'offset': 188 | if ins.opcode in (opcodes.OPCODE_ST, opcodes.OPCODE_LD): 189 | val = twos_comp(val, 11) 190 | 191 | field_details.append((field, val, extra)) 192 | 193 | return field_details 194 | -------------------------------------------------------------------------------- /tools/disassemble.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | from uctypes import struct, addressof, LITTLE_ENDIAN, UINT16, UINT32 9 | import ubinascii 10 | import sys 11 | 12 | 13 | # placeholders: 14 | # these functions will be dynamically loaded later based on the chosen cpu 15 | decode_instruction, get_instruction_fields = None, None 16 | 17 | 18 | def load_decoder(cpu): 19 | if cpu == 'esp32': 20 | mod = 'decode' 21 | elif cpu == 'esp32s2': 22 | mod = 'decode_s2' 23 | else: 24 | raise ValueError('Invalid cpu') 25 | 26 | relative_import = 1 if '/' in __file__ else 0 27 | decode = __import__(mod, globals(), locals(), [], relative_import) 28 | 29 | global decode_instruction, get_instruction_fields 30 | decode_instruction = decode.decode_instruction 31 | get_instruction_fields = decode.get_instruction_fields 32 | 33 | 34 | def chunk_into_words(code, bytes_per_word, byteorder): 35 | chunks = [ 36 | ubinascii.hexlify(code[i:i + bytes_per_word]) 37 | for i in range(0, len(code), bytes_per_word) 38 | ] 39 | 40 | words = [int.from_bytes(ubinascii.unhexlify(i), byteorder) for i in chunks] 41 | 42 | return words 43 | 44 | 45 | def print_ulp_header(h): 46 | print('header') 47 | print('ULP magic : %s (0x%08x)' % (h.magic.to_bytes(4, 'little'), h.magic)) 48 | print('.text offset : %s (0x%02x)' % (h.text_offset, h.text_offset)) 49 | print('.text size : %s (0x%02x)' % (h.text_size, h.text_size)) 50 | print('.data offset : %s (0x%02x)' % (h.text_offset+h.text_size, h.text_offset+h.text_size)) 51 | print('.data size : %s (0x%02x)' % (h.data_size, h.data_size)) 52 | print('.bss size : %s (0x%02x)' % (h.bss_size, h.bss_size)) 53 | print('----------------------------------------') 54 | 55 | 56 | def print_code_line(byte_offset, i, asm): 57 | lineformat = '{0:04x} {1} {2}' 58 | hex = ubinascii.hexlify(i.to_bytes(4, 'little')) 59 | print(lineformat.format(byte_offset, hex.decode('utf-8'), asm)) 60 | 61 | 62 | def decode_instruction_and_print(byte_offset, i, verbose=False): 63 | try: 64 | ins, name = decode_instruction(i) 65 | except Exception as e: 66 | print_code_line(byte_offset, i, e) 67 | return 68 | 69 | print_code_line(byte_offset, i, name) 70 | 71 | if verbose: 72 | for field, val, extra in get_instruction_fields(ins): 73 | print(" {:10} = {:3}{}".format(field, val, extra)) 74 | 75 | 76 | def print_text_section(code, verbose=False): 77 | print('.text') 78 | 79 | words = chunk_into_words(code, bytes_per_word=4, byteorder='little') 80 | 81 | for idx, i in enumerate(words): 82 | decode_instruction_and_print(idx << 2,i , verbose) 83 | 84 | 85 | def print_data_section(data_offset, code): 86 | print('.data') 87 | 88 | words = chunk_into_words(code, bytes_per_word=4, byteorder='little') 89 | 90 | for idx, i in enumerate(words): 91 | asm = "" if i == 0 else "" 92 | print_code_line(data_offset + (idx << 2), i, asm) 93 | 94 | 95 | def disassemble_manually(byte_sequence_string, cpu, verbose=False): 96 | load_decoder(cpu) 97 | 98 | sequence = byte_sequence_string.strip().replace(' ','') 99 | chars_per_instruction = 8 100 | list = [ 101 | sequence[i:i+chars_per_instruction] 102 | for i in range(0, len(sequence), chars_per_instruction) 103 | ] 104 | 105 | for idx, instruction in enumerate(list): 106 | byte_sequence = ubinascii.unhexlify(instruction.replace(' ','')) 107 | i = int.from_bytes(byte_sequence, 'little') 108 | decode_instruction_and_print(idx << 2, i, verbose) 109 | 110 | 111 | def disassemble_file(filename, cpu, verbose=False): 112 | load_decoder(cpu) 113 | 114 | with open(filename, 'rb') as f: 115 | data = f.read() 116 | 117 | binary_header_struct_def = dict( 118 | magic = 0 | UINT32, 119 | text_offset = 4 | UINT16, 120 | text_size = 6 | UINT16, 121 | data_size = 8 | UINT16, 122 | bss_size = 10 | UINT16, 123 | ) 124 | h = struct(addressof(data), binary_header_struct_def, LITTLE_ENDIAN) 125 | 126 | if (h.magic != 0x00706c75): 127 | print('Invalid signature: 0x%08x (should be: 0x%08x)' % (h.magic, 0x00706c75)) 128 | return 129 | 130 | if verbose: 131 | print_ulp_header(h) 132 | 133 | code = data[h.text_offset:(h.text_offset+h.text_size)] 134 | print_text_section(code, verbose) 135 | 136 | if verbose: 137 | print('----------------------------------------') 138 | 139 | data_offset = h.text_offset+h.text_size 140 | code = data[data_offset:(data_offset+h.data_size)] 141 | print_data_section(data_offset-h.text_offset, code) 142 | 143 | 144 | def print_help(): 145 | print('Usage: disassemble.py [] [-m | ]') 146 | print('') 147 | print('Options:') 148 | print(' -c Choose ULP variant: either esp32 or esp32s2') 149 | print(' -h Show this help text') 150 | print(' -m Sequence of hex bytes (8 per instruction)') 151 | print(' -v Verbose mode. Show ULP header and fields of each instruction') 152 | print(' Path to ULP binary') 153 | pass 154 | 155 | 156 | def handle_cmdline(params): 157 | cpu = 'esp32' 158 | verbose = False 159 | filename = None 160 | byte_sequence = None 161 | 162 | while params: 163 | if params[0] == '-h': 164 | print_help() 165 | sys.exit(0) 166 | elif params[0] == '-c': 167 | cpu = params[1] 168 | params = params[1:] # remove first param from list 169 | elif params[0] == '-m': 170 | if len(params) == 1: 171 | print_help() 172 | sys.exit(1) 173 | params = params[1:] # remove -m from list 174 | 175 | sequence_len = len(params) 176 | for i in range(0, len(params)): 177 | if params[i][0] == '-': # start of a next option 178 | sequence_len = i-1 179 | break 180 | 181 | if sequence_len < 0: 182 | print_help() 183 | sys.exit(1) 184 | 185 | byte_sequence = "".join(params[:sequence_len+1]) 186 | params = params[sequence_len:] 187 | elif params[0] == '-v': 188 | verbose = True 189 | elif params[0][0] == '-': 190 | # ignore unknown options for now 191 | pass 192 | else: 193 | if not filename: 194 | filename = params[0] 195 | 196 | params = params[1:] # remove first param from list 197 | 198 | 199 | if byte_sequence: 200 | disassemble_manually(byte_sequence, cpu, verbose) 201 | elif filename: 202 | disassemble_file(filename, cpu, verbose) 203 | 204 | 205 | if sys.argv: # if run from cmdline 206 | handle_cmdline(sys.argv[1:]) 207 | -------------------------------------------------------------------------------- /tools/esp32_ulp: -------------------------------------------------------------------------------- 1 | ../esp32_ulp -------------------------------------------------------------------------------- /tools/genpkgjson.py: -------------------------------------------------------------------------------- 1 | # 2 | # This file is part of the micropython-esp32-ulp project, 3 | # https://github.com/micropython/micropython-esp32-ulp 4 | # 5 | # SPDX-FileCopyrightText: 2018-2023, the micropython-esp32-ulp authors, see AUTHORS file. 6 | # SPDX-License-Identifier: MIT 7 | 8 | """ 9 | Tool for generating package.json for the MIP package manager 10 | 11 | Run this tool from the repo root, like: 12 | 13 | python tools/genpkgjson.py > package.json 14 | 15 | Note: 16 | This tool works with both python3 and micropyton. 17 | """ 18 | 19 | import os 20 | import json 21 | 22 | PACKAGE_JSON_VERSION=1 23 | 24 | # Update the repo when working with a fork 25 | GITHUB_REPO="micropython/micropython-esp32-ulp" 26 | 27 | 28 | def get_files(path): 29 | files = [f'{path}/{f}' for f in os.listdir(path)] 30 | return files 31 | 32 | 33 | def build_urls(repo_base, files): 34 | return [[f, f'github:{repo_base}/{f}'] for f in files] 35 | 36 | 37 | def print_package_json(urls): 38 | """ 39 | Custom-formatting JSON output for better readability 40 | 41 | json.dumps in MicroPython cannot format the output and python3 42 | puts each element of each urls' sub-arrays onto a new line. 43 | Here we print each file and its source url onto the same line. 44 | """ 45 | print('{') 46 | print(f' "v":{PACKAGE_JSON_VERSION},') 47 | print(' "urls":[') 48 | print(',\n'.join([f' {json.dumps(u)}' for u in sorted(urls)])) 49 | print(' ]') 50 | print('}') 51 | 52 | 53 | if __name__ == '__main__': 54 | library_root = 'esp32_ulp' 55 | files = get_files(library_root) 56 | urls = build_urls(GITHUB_REPO, files) 57 | print_package_json(urls) 58 | --------------------------------------------------------------------------------