-
Notifications
You must be signed in to change notification settings - Fork 25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ESP32-S2/S3 support #91
Conversation
…s_s2 Currently the logic in these copies is identical to the original. This will makes it easier to see the changes we're making in subsequent commits.
Select cpu with -c when running the assembler (--mcpu as used by Espressif's esp32ulp-elf-as also works). The possible values are 'esp32' and 'esp32s2'. (Note esp32s2 also works for the ESP32-S3, because those two MCUs share the same ULP-FSM binary format). If no cpu is specified the original 'esp32' will be used as before.
In summary, these are the most important changes: * the `sub_opcode` field on all opcodes except ST have been shrunk from 3 bits down to 2 bits * the LD and ST opcodes support more variations (new instructions) * branching instructions have changed. The JUMPR instruction uses different comparisons and the JUMPS instruction now implements all comparisons in hardware, without requiring multiple instructions to emulate any comparison. * There is no more SLEEP instruction to select a sleep timer. Since Espressif chose to simply convert SLEEP instructions into WAIT instructions we're doing the same. Update integration tests to run for both the ESP32 and ESP32-S2.
The disassembler is now mainly the command line tool, which deals with interpreting user input, formatting output, etc. This allows us to add decoding logic for new cpus (the S2) and the disassembler can then dynamically load the correct decoding module.
Currently only the esp32 is implemented (esp32s2 soon to follow). This commit adds the -c command line option to the disassembler, as well as updates the integration tests to supply the cpu parameter, and test fixtures are renamed to include the cpu type in their name, so that we can have separate fixture files for other cpus.
To disassemble ESP32-S2 ULP binaries use the `-c esp32s2` option when running the disassembler. Update documentation to mention support for the ESP32-S2.
…pported by the S2 The ESP32-S2 changes the comparison operators that are natively supported by the CPU for the JUMPR and JUMPS instruction. For the JUMPS instruction all comparison operators are now natively supported in hardware.
For example STL and STH can be used to store to the lower or upper 16-bits of a memory address respectively. (The original ST instruction could only store to the lower 16-bits of a memory address) The following new instructions are now supported: * LDL, LDH * STL, STH, ST32, STI32, STI, STO
The following new instructions are now supported: * LDL, LDH * STL, STH, ST32, STI32, STI, STO Note: The disassembler will return LD instead of LDL and ST instead of STL, because they are each synonyms of the other. We can only pick either and so we picked the keyword that exists across both the ESP32 and the ESP32-S2/S3.
There are some parts of the implementation I am currently not 100% happy with:
@ThomasWaldmann @dpgeorge @mattytrentini - if you have time and want to take a look at this PR, i'd appreciate it. |
One thing I noticed is that the S2 and S3 have different peripheral register addresses for similar things, different from the original ESP32 and also different between each other. That means that (most likely) our I will test this on my S2 and S3 and add examples for the specific versions and add a mention of this detail into the documentation. |
Specifying 0 as the label is different than not specifying a label at all. This commit corrects the behaviour when label 0 is used. Also run the all_opcodes fixture as integration test to ensure the same result as binutils-gdb/esp32ulp-as (which is how this bug was found).
Hello all, first of all thank you @wnienhaus for this usefull PR. We were in need of a Pulse Count implementation in ULP for ESP32S3 so based on this PR we changed the required addressed and it worked. (we were not sure whether to send a PR on wnienhaus:s2s3_support or on this main repo so we decided to just write this post as a description of the solution.) The changes can be found in the last two commits of the following repo which has this PR merged and has as addition the specifics for ESP32S3: In a nutshell, opcodes_s3.py and soc_s3.py were added and assemble.py was updated to identify cpu=esp32s3. Also here is the exampel code used to test the Pulse Counter on ESP32S3 on GPIO 4. We are based on micropython 1.19.1 so we made changes in the code of micropython to enable ULP for ESP32S3. For this reason, we added one more function in the ULP module which initializes the GPIO port, defines it as Input, enables Pull Down and instructs to keep RTC peripherals powered on when in deep sleep. This is called every time the device wakes up from deep sleep or else the counting of the edges stops. The call to this function: https://github.com/insighio/micropython-esp32-ulp/blob/90fe843783ac63acf6224bd3e9e11b3522ce5469/examples/pulse_counter_esp32s3.py#L117-L124 And the function itself in custom micropython branch: In this way, we have a functioning Pulse Counter. Though our tests, by setting the ulp.set_wakeup_period from 20.000 to 100 we are reliably reading 100 pulses per second. Hope all the above are helpfull. Thanks one again. |
Updated as per ESP-IDF v5.0.2. Also added reference URL to those constants in the ESP-IDF.
@ftylitak Thank you so much for that feedback and testing this PR. Great to know that it works for you. I have been working slowly on improving the PR to add support for the S2 and S3 peripheral register addresses. I just pushed that work to this PR. I took a different approach than you for supporting the S3. Since the S2 and S3 have the same binary format on the ULP-FSM (in order words That choice did lead me to something I am not 100% happy with, but thought it might be overall positive nonetheless, namely that opcode_s2 now accepts peripheral register addresses from any of the ESP32 variants. The reason is that what ends up in the actual processor instruction is a relative offset, relative to the base address of the specific variant. Thus within the instruction, it doesn't matter what the real input address was. And since especially across the S2 and S3 most of the relative offsets are actually the same, this "feature" allows assembly code with register addresses in the S2 range to work unmodified for the S3 (actually the binary would even be the same). You can see in the examples I added that I now only need 1 example for the s2/s3 combined rather than 2 examples. I am not sure what you think of this. You took the alternative of adding a new cpu Feel free to rebase onto what I just pushed. Note that commit 2aff8a1 contains an important fix for ST instructions with labels. Small comment on your Micropython patch: That function is specific to your need and would not be good in MicroPython in general (e.g. it hard codes the direction to input, etc). However, it might not even be needed, because so far I have always managed to do all that initialisation inside the ULP code, given that it also has access to the peripheral registers, which are configured by the What might not work (I am still busy testing), is enabling power of the RTC peripherals from ULP code (especially once deep_sleep has already been entered). So perhaps that is the "generic" function that would be useful in MicroPython, i.e. this part: // instruct to keep RTC peripherals powered on after when in deep sleep
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); |
@wnienhaus great comments. Indeed our modification in the Micropython branch is too specific. Those parts of setting the direction of the input etc were added there in the beginning as testing because we were not getting any results (plus we did not have much knowledge on the ULP so it was a bit of testing here and there). In the end we have found the RTC peripheral power on, it all started working though we did not take a step back to use the ULP code for the rest. The interesting part is that the ESP-IDF example does not use esp_sleep_pd_config though it works. So, may be it is a setting of Micropython that affects the power of the RTC peripherals. Regading to this project (micropython-esp32-ulp) we wil rebase to your changes. |
Thanks for that input. It should help me resolve my current problem: Very interesting that the ESP-IDF example does not use the So I will actually try your approach next (how you patched MicroPython), given that it works for you, and then see if that makes |
@ftylitak So, it turns out it's actually the "IO MUX clock gate" that needs to be enabled, which the ESP-IDF does as part of
SENS.sar_peri_clk_gate_conf.iomux_clk_en = 1; When I create a MicroPython function in I tried doing the equivalent with One "simple" fix could be to patch MicroPython to set this flag everytime The other, perhaps "more correct" option could be to simply expose the Anyway, just reporting my findings so far. Will still think a bit more about this. (@ftylitak - if you have some time, it would be interesting to know whether this single line is enough to make your use case work, also in deepsleep, because i have not tested with deepsleep yet.) |
Peripheral registers of the ESP32-S2 and S3 are at a different location than those of the original ESP32. The location within the address space is mostly the same, however we need to use the correct base address when calculating periph_sel (type of register) used in REG_RD and REG_WR instructions. Note 1: To avoid creating an entirely new CPU (esp32s3) just for handling the different peripheral register addresses of the S3, while their binary format (instruction set) is identical, our esp32s2 CPU support will now accept both ESP32-S2 and ESP32-S3 addresses. This should make a binary for the one seamlessly work on the other (without reassembly), given that the offsets of different peripheral registers between the S2 and S3 are mostly (but not entirely) identical. Note 2: Our esp32s2 cpu support will also accept peripheral register addresses of the original ESP32. This was originally done because Espressif's binutils-gdb/esp32-ulp-as incorrectly validates addresses for the esp32s2 cpu, and to make our compat tests pass, this was needed. However this also has a nice side- effect of allowing some assembly written for the original ESP32 to work unmodified when assembled for an S2/S3, because some of the peripheral registers live at the same offset from the base for all three variants.
We also now use the correct include files from the ESP-IDF when building the defines DB, correct for the cpu type we're testing with. (That also means the defines DB is built once per cpu type). That, together with some ESP32-S2 specific test cases from Espressif's esp32s2 assembler test-suite, make those test cases more interesting to run, compared to only assembling ESP32 examples with the esp32s2 cpu selected. Note: This change no longer runs the ulp_tool examples for the esp32s2 case, because those examples use contants (from the ESP-IDF include files), which no longer exist for the ESP32-S2, such as `RTC_IO_TOUCH_PAD*_HOLD_S`. Since the ulp_tool examples primarily test the preprocessor's ability to resolve constants from include files (via the defines DB), testing those examples only once with the ESP32 cpu should be enough.
4af34b3
to
eb9fdcd
Compare
So, I finally have made progress. It turns out the issue with failing to write to the This issue also once existed in the ESP-IDF, which is where we got the incorrect translation logic from. After asking Espressif (espressif/esp-idf#12158) about not being able to write to the I have now added the fix to this PR and added working examples (all tested on a real device) to the So we don't need any changes to MicroPython after all! I am happy. cc: @ftylitak |
5be3f10
to
bc2ee7e
Compare
The ESP32-S2/S3 support a negative offset in ST/LD instructions. Those offsets are two's-complement encoded into a field that is 11-bits wide. This change corrects the decoding of negative offsets given the field width of just 11-bits, rather than relying on the 32 or 64 bits of a MicroPython `int`. Note 1: Negative offsets used in JUMP instructions are encoded differently (sign bit + positive value), and their decoding is already done correctly. Note 2: The LD/ST instructions in of the ESP32 do not support negative offsets (according to Espressif tests), so their decoding remains as is.
This was already incorrect in the original ESP32 implementation but was discovered while testing the new S2/S3 implementation. This was also wrong within the ESP-IDF, that we based the translation logic on. Espressif fixed the issue in this pull request: espressif/esp-idf#11652 We now also have unit tests and compat (integration) tests, that compare our binary output against that of binutils-gdb/esp32-ulp-as, which already did this translation correctly, but we didnt have a test for the specific cases we handled incorrectly, so we didn't notice this bug. This fix has also been tested on a real device, because S2/S3 devices need the IOMUX clock enabled in order to be able to read GPIO input from the ULP, and enabling that clock required writing to a register in the SENS address range, which didnt work correctly before this fix.
There are now example files for the S2 and S3 (with ``_s2`` or ``_s3`` appended to their filenames). Note: The s2 examples also work unmodified on the ESP32-S3, except the readgpio example which needs different peripheral register addresses on the S3. The ``counter_s2.py`` example is unmodified compared to the original example, except that the assembler is told to generate esp32s2 output. The ``blink_s2.py``, ``readgpio_s2.py`` and ``readgpio_s3.py`` examples have their rtc_io base address updated, as well as the constants referring to the GPIO pins and channels and the peripheral register bits used to read/write the GPIO inputs/outputs. These addresses/bits have changed from the original ESP32. Otherwise the examples are identical to the examples for the original ESP32.
bc2ee7e
to
debff30
Compare
Now that the examples work on real devices (S2 and S3) and because I have tested this quite extensively by now, I will proceed to merge this. Any further improvements can follow in future PRs. |
This PR adds support for the ULP-FSM (not ULP RISC-V) of the ESP32-S2 and ESP32-S3.
(Note, the ESP32-S2 and S3 have the same ULP-FSM, so we don't distinguish between them in the implementation).
The entire instruction set of the ULP-FSM of the ESP32-S2 is supported, including the new load and store instructions to allow loading from or storing to the upper 16-bits of memory locations.
Use the
-c
command line argument when assembling or disassembling. Use-c esp32
(or omit the-c
option) to select the original ESP32. Use-c esp32s2
to select the ESP32-S2/S3. Refer to the documentation indocs/disassembler.rst
for more detail.Please note: the latest release of MicroPython to date is
1.20,
which does not enable the ULP on ESP32-S2/S3 devices. This is fixed already, but we need to wait for the next release to have the fix in an official version. To test this on a real device, use a recent nightly build of Micropython and flash that to your device.This PR resolves #85 .
(The easiest way to review this PR is commit-by-commit. Each commit is one independent change.)