Skip to content

Commit 25bf34e

Browse files
authored
Merge pull request #91 from wnienhaus/s2s3_support
ESP32-S2/S3 support
2 parents 5c4d016 + debff30 commit 25bf34e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+3414
-344
lines changed

.gitignore

+13-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1-
tests/compat/*.bin
2-
tests/compat/*.elf
3-
tests/compat/*.o
4-
tests/compat/*.ulp
5-
tests/compat/*.log
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
614
demo.ulp
715
*.pyc
816
*.pyo

README.rst

+23-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ micropython-esp32-ulp is an assembler toolchain for the ESP32 ULP (Ultra Low-Pow
1515
Co-Processor, written in MicroPython.
1616

1717
It can translate small assembly language programs to a loadable/executable
18-
ULP machine code binary, directly on the ESP32 microcontroller.
18+
ULP-FSM (not RISC-V) machine code binary, directly on a ESP32 microcontroller.
1919

2020
This is intended as an alternative approach to assembling such programs using
2121
the `binutils-gdb toolchain <https://github.com/espressif/binutils-gdb/tree/esp32ulp-elf-2.35>`_
@@ -30,13 +30,30 @@ Features
3030
The following features are supported:
3131

3232
* the entire `ESP32 ULP instruction set <https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/ulp_instruction_set.html>`_
33+
* the entire `ESP32-S2 ULP instruction set <https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/system/ulp_instruction_set.html>`_
34+
(this also covers the ESP32-S3) [#f1]_ [#f2]_
3335
* constants defined with ``.set``
3436
* constants defined with ``#define``
3537
* expressions in assembly code and constant definitions
3638
* RTC convenience macros (e.g. ``WRITE_RTC_REG``)
3739
* many ESP32 ULP code examples found on the web will work unmodified
3840
* a simple disassembler is also provided
3941

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 </docs/index.rst>`_) 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+
4057
4158
Quick start
4259
-----------
@@ -66,10 +83,12 @@ See `docs/index.rst </docs/index.rst>`_.
6683
Requirements
6784
------------
6885

69-
The minimum supported version of MicroPython is v1.12.
86+
The minimum supported version of MicroPython is v1.12. (For ESP32-S2 and S3
87+
devices, a version greater than v1.20 is required as versions before that
88+
did not enable the ``esp32.ULP`` class).
7089

71-
An ESP32 is required to run the ULP machine code binary produced by micropython-esp32-ulp
72-
(the ESP32-S2 will not work as it is not binary compatible with the ESP32).
90+
An ESP32 device is required to run the ULP machine code binary produced by
91+
micropython-esp32-ulp.
7392

7493

7594
License

docs/disassembler.rst

+26-9
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ You can also specify additional options to ``disassemble.py`` as follows:
2525
+--------------------------+----------------------------------------------------------------+
2626
| Option | Description |
2727
+==========================+================================================================+
28+
| ``-c`` or ``--mcpu`` | Choose ULP variant: either esp32 or esp32s2 |
29+
+--------------------------+----------------------------------------------------------------+
2830
| ``-h`` | Show help text |
2931
+--------------------------+----------------------------------------------------------------+
3032
|| ``-m <bytes sequence>`` || Disassemble a provided sequence of hex bytes |
@@ -43,18 +45,31 @@ specified file.
4345
Note that the ULP header is validates and files with unknown magic bytes will be
4446
rejected. The correct 4 magic bytes at the start of a ULP binary are ``ulp\x00``.
4547

46-
Example:
48+
Example disassembling an ESP32 ULP binary:
4749

4850
.. code-block:: shell
4951
5052
$ micropython -m tools.disassemble path/to/binary.ulp
5153
.text
5254
0000 040000d0 LD r0, r1, 0
53-
0004 0e0400d0 LD r2, r3, 1
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 <empty>
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
5469
0008 84010068 ST r0, r1, 0
55-
000c 8b090068 ST r3, r2, 2
70+
000c 8b010068 ST r3, r2, 0
5671
.data
57-
0000 00000000 <empty>
72+
0010 00000000 <empty>
5873
5974
6075
Disassembling a byte sequence
@@ -129,18 +144,20 @@ For example:
129144
Disassembling on device
130145
-----------------------------
131146

132-
The disassembler also works when used on an ESP32.
147+
The disassembler also works when used on an ESP32 device.
133148

134149
To use the disassembler on a real device:
135150

136151
* ensure ``micropython-esp32-ulp`` is installed on the device (see `docs/index.rst </docs/index.rst>`_).
137-
* upload ``tools/disassemble.py`` to the device (any directory will do)
138-
* run the following:
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):
139156

140157
.. code-block:: python
141158
142159
from disassemble import disassemble_file
143160
# then either:
144-
disassemble_file('path/to/file.ulp') # normal mode
161+
disassemble_file('path/to/file.ulp', cpu='esp32s2') # normal mode
145162
# or:
146-
disassemble_file('path/to/file.ulp', True) # verbose mode
163+
disassemble_file('path/to/file.ulp', cpu='esp32s2', verbose=True) # verbose mode

docs/index.rst

+12-4
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ follows:
5858
cd micropython-esp32-ulp
5959
micropython -m esp32_ulp path/to/code.S # this results in path/to/code.ulp
6060
61+
The assembler supports selecting a CPU to assemble for using the ``-c`` option
62+
(valid cpu's are ``esp32`` and ``esp32s2``):
63+
64+
.. code-block:: shell
65+
66+
micropython -m esp32_ulp -c esp32s2 path/to/code.S # assemble for an ESP32-S2
67+
6168
6269
More examples
6370
+++++++++++++
@@ -86,12 +93,13 @@ assembly source file into a machine code binary file with a ``.ulp`` extension.
8693
That file can then be loaded directly without assembling the source again.
8794

8895
1. Create/upload an assembly source file and run the following to get a
89-
loadable ULP binary as a ``.ulp`` file:
96+
loadable ULP binary as a ``.ulp`` file (specify ``cpu='esp32s2'`` if you
97+
have an ESP32-S2 or ESP32-S3 device):
9098

9199
.. code-block:: python
92100
93101
import esp32_ulp
94-
esp32_ulp.assemble_file('code.S') # this results in code.ulp
102+
esp32_ulp.assemble_file('code.S', cpu='esp32') # this results in code.ulp
95103
96104
2. The above prints out the offsets of all global symbols/labels. For the next
97105
step, you will need to note down the offset of the label, which represents
@@ -153,7 +161,6 @@ Currently the following are not supported:
153161
* assembler macros using ``.macro``
154162
* preprocessor macros using ``#define A(x,y) ...``
155163
* including files using ``#include``
156-
* ESP32-S2 (not binary compatible with the ESP32)
157164

158165

159166
Testing
@@ -164,7 +171,8 @@ output is identical with what Espressif's esp32-elf-as (from their `binutils-gdb
164171
<https://github.com/espressif/binutils-gdb/tree/esp32ulp-elf-2.35>`_) produces.
165172

166173
micropython-esp32-ulp has been tested on the Unix port of MicroPython and on real ESP32
167-
devices with the chip type ESP32D0WDQ6 (revision 1) without SPIRAM.
174+
devices with the chip type ESP32D0WDQ6 (revision 1) without SPIRAM as well as ESP32-S2
175+
(ESP32-S2FH4) and ESP32-S3 (ESP32-S3R8) devices.
168176

169177
Consult the Github Actions `workflow definition file </.github/workflows/run_tests.yaml>`_
170178
for how to run the different tests.

docs/preprocess.rst

+16
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,21 @@ are not needed on the device either.)
9595
micropython -m esp32_ulp.parse_to_db \
9696
esp-idf/components/soc/esp32/include/soc/{soc,soc_ulp,rtc_cntl_reg,rtc_io_reg,sens_reg}.h
9797
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+
98113
2. Using the defines database during preprocessing
99114

100115
The preprocessor will automatically use a defines database, when using the
@@ -108,6 +123,7 @@ are not needed on the device either.)
108123
or instantiate the ``Preprocessor`` class directly, without passing it a
109124
DefinesDB instance via ``use_db``.
110125

126+
111127
Design choices
112128
--------------
113129

esp32_ulp/__init__.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
garbage_collect('after import')
77

88

9-
def src_to_binary(src):
10-
assembler = Assembler()
9+
def src_to_binary(src, cpu):
10+
assembler = Assembler(cpu)
1111
src = preprocess(src)
1212
assembler.assemble(src, remove_comments=False) # comments already removed by preprocessor
1313
garbage_collect('before symbols export')
@@ -19,11 +19,11 @@ def src_to_binary(src):
1919
return make_binary(text, data, bss_len)
2020

2121

22-
def assemble_file(filename):
22+
def assemble_file(filename, cpu):
2323
with open(filename) as f:
2424
src = f.read()
2525

26-
binary = src_to_binary(src)
26+
binary = src_to_binary(src, cpu)
2727

2828
if filename.endswith('.s') or filename.endswith('.S'):
2929
filename = filename[:-2]

esp32_ulp/__main__.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,18 @@
22
from . import assemble_file
33

44

5-
def main(fn):
6-
assemble_file(fn)
5+
def main(fn, cpu):
6+
assemble_file(fn, cpu)
77

88

99
if __name__ == '__main__':
10-
main(sys.argv[1])
10+
cpu = 'esp32'
11+
filename = sys.argv[1]
12+
if len(sys.argv) > 3:
13+
if sys.argv[1] in ('-c', '--mcpu'):
14+
cpu = sys.argv[2].lower()
15+
if cpu not in ('esp32', 'esp32s2'):
16+
raise ValueError('Invalid cpu')
17+
filename = sys.argv[3]
18+
main(filename, cpu)
1119

esp32_ulp/assemble.py

+15-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
"""
44

55
import re
6-
from . import opcodes
76
from .nocomment import remove_comments as do_remove_comments
87
from .util import garbage_collect
98

@@ -88,9 +87,19 @@ def set_global(self, symbol):
8887

8988
class Assembler:
9089

91-
def __init__(self, symbols=None, bases=None, globals=None):
90+
def __init__(self, cpu='esp32', symbols=None, bases=None, globals=None):
91+
if cpu == 'esp32':
92+
opcode_module = 'opcodes'
93+
elif cpu == 'esp32s2':
94+
opcode_module = 'opcodes_s2'
95+
else:
96+
raise ValueError('Invalid cpu')
97+
98+
relative_import = 1 if '/' in __file__ else 0
99+
self.opcodes = __import__(opcode_module, None, None, [], relative_import)
100+
92101
self.symbols = SymbolTable(symbols or {}, bases or {}, globals or {})
93-
opcodes.symbols = self.symbols # XXX dirty hack
102+
self.opcodes.symbols = self.symbols # XXX dirty hack
94103

95104
# regex for parsing assembly lines
96105
# format: [[whitespace]label:][whitespace][opcode[whitespace arg[,arg...]]]
@@ -223,7 +232,7 @@ def d_align(self, align=4, fill=None):
223232
self.fill(self.section, amount, fill)
224233

225234
def d_set(self, symbol, expr):
226-
value = int(opcodes.eval_arg(expr))
235+
value = int(self.opcodes.eval_arg(expr))
227236
self.symbols.set_sym(symbol, ABS, None, value)
228237

229238
def d_global(self, symbol):
@@ -264,13 +273,13 @@ def assembler_pass(self, lines):
264273
else:
265274
# machine instruction
266275
opcode_lower = opcode.lower()
267-
func = getattr(opcodes, 'i_' + opcode_lower, None)
276+
func = getattr(self.opcodes, 'i_' + opcode_lower, None)
268277
if func is not None:
269278
if self.a_pass == 1:
270279
# during the first pass, symbols are not all known yet.
271280
# so we add empty instructions to the section, to determine
272281
# section sizes and symbol offsets for pass 2.
273-
result = (0,) * opcodes.no_of_instr(opcode_lower, args)
282+
result = (0,) * self.opcodes.no_of_instr(opcode_lower, args)
274283
else:
275284
result = func(*args)
276285

esp32_ulp/opcodes.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ def i_reg_wr(reg, high_bit, low_bit, val):
379379
_wr_reg.addr = reg & 0xff
380380
_wr_reg.periph_sel = (reg & 0x300) >> 8
381381
else:
382-
_wr_reg.addr = (reg & 0xff) >> 2
382+
_wr_reg.addr = (reg >> 2) & 0xff
383383
_wr_reg.periph_sel = _soc_reg_to_ulp_periph_sel(reg)
384384
_wr_reg.data = get_imm(val)
385385
_wr_reg.low = get_imm(low_bit)
@@ -394,7 +394,7 @@ def i_reg_rd(reg, high_bit, low_bit):
394394
_rd_reg.addr = reg & 0xff
395395
_rd_reg.periph_sel = (reg & 0x300) >> 8
396396
else:
397-
_rd_reg.addr = (reg & 0xff) >> 2
397+
_rd_reg.addr = (reg >> 2) & 0xff
398398
_rd_reg.periph_sel = _soc_reg_to_ulp_periph_sel(reg)
399399
_rd_reg.unused = 0
400400
_rd_reg.low = get_imm(low_bit)

0 commit comments

Comments
 (0)