Skip to content

Commit 13049da

Browse files
committed
add documentation for the preprocessor
This defines what the preprocessor aims to do, why and what its intentional limitations are. Examples on how to use it and how to use the "Defines DB" are also provided
1 parent 69ae946 commit 13049da

File tree

2 files changed

+143
-0
lines changed

2 files changed

+143
-0
lines changed

Diff for: README.rst

+5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ Constants defined with ``.set`` are supported in expressions.
2424
We have some unit tests and also compatibility tests that compare the output
2525
whether it is identical with binutils-esp32ulp output.
2626

27+
There is a simple preprocessor that understands just enough to allow assembling
28+
ULP source files containing convenience macros such as WRITE_RTC_REG. The
29+
preprocessor and how to use it is documented here:
30+
`Preprocessor support <docs/preprocess.rst>`_.
31+
2732
There might be some stuff missing, some bugs and other symptoms of alpha
2833
software. Also, error and exception handling is rather rough yet.
2934

Diff for: docs/preprocess.rst

+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
Preprocessor
2+
---------------------
3+
4+
py-esp32-ulp contains a small preprocessor, which aims to fulfill one goal:
5+
facilitate assembling of ULP code from Espressif and other open-source
6+
projects to loadable/executable machine code without modification.
7+
8+
Such code uses convenience macros (``READ_RTC_*`` and ``WRITE_RTC_*``)
9+
provided by the ESP-IDF framework, along with constants defined in the
10+
framework's include files (such as ``RTC_GPIO_IN_REG``), to make reading
11+
and writing from/to peripheral registers much easier.
12+
13+
In order to do this the preprocessor has two capabilities:
14+
15+
1. Parse and replace identifiers defined with ``#define``
16+
2. Recognise the ``WRITE_RTC_*`` and ``READ_RTC_*`` macros and expand
17+
them in a way that mirrors what the real ESP-IDF macros do.
18+
19+
20+
Usage
21+
------------------------
22+
23+
Normally the assembler is called as follows
24+
25+
.. code-block:: python
26+
27+
src = "..full assembler file contents"
28+
assembler = Assembler()
29+
assembler.assemble(src)
30+
...
31+
32+
With the preprocessor, simply pass the source code via the preprocessor first:
33+
34+
.. code-block:: python
35+
36+
from preprocess import preprocess
37+
38+
src = "..full assembler file contents"
39+
src = preprocess(src)
40+
assembler = Assembler()
41+
assembler.assemble(src)
42+
...
43+
44+
45+
Using a "Defines Database"
46+
--------------------------
47+
48+
Because the py-esp32-ulp assembler was built for running on the ESP32
49+
microcontroller with limited RAM, the preprocessor aims to work there too.
50+
51+
To handle large number of defined constants (such as the ``RTC_*`` constants from
52+
the ESP-IDF) the preprocessor can use a database (based on BerkleyDB) stored on the
53+
device's filesystem for looking up defines.
54+
55+
The database needs to be populated before preprocessing. (Usually, when only using
56+
constants from the ESP-IDF, this is a one-time step, because the include files
57+
don't change.) The database can be reused for all subsequent preprocessor runs.
58+
59+
(The database can also be generated on a PC and then deployed to the ESP32, to
60+
save processing effort on the device. In that case the include files themselves
61+
are not needed on the device either.)
62+
63+
1. Build the defines database
64+
65+
The ``esp32_ulp.parse_to_db`` tool can be used to generate the defines
66+
database from include files. The resulting file will be called
67+
``defines.db``.
68+
69+
(The following assume running on a PC. To do this on device, refer to the
70+
`esp32_ulp/parse_to_db.py <../esp32_ulp/parse_to_db.py>`_ file.)
71+
72+
.. code-block:: bash
73+
74+
# general command
75+
micropython -m esp32_ulp.parse_to_db path/to/include.h
76+
77+
# loading specific ESP-IDF include files
78+
micropython -m esp32_ulp.parse_to_db esp-idf/components/soc/esp32/include/soc/soc_ulp.h
79+
80+
# loading multiple files at once
81+
micropython -m esp32_ulp.parse_to_db esp-idf/components/soc/esp32/include/soc/*.h
82+
83+
# if file system space is not a concern, the following can be convenient
84+
# by including all relevant include files from the ESP-IDF framework.
85+
# This results in an approximately 2MB large database.
86+
micropython -m esp32_ulp.parse_to_db \
87+
esp-idf/components/soc/esp32/include/soc/*.h \
88+
esp-idf/components/esp_common/include/*.h
89+
90+
# most ULP code uses only 5 include files. Parsing only those into the
91+
# database should thus allow assembling virtually all ULP code one would
92+
# find or want to write.
93+
# This results in an approximately 250kB large database.
94+
micropython -m esp32_ulp.parse_to_db \
95+
esp-idf/components/soc/esp32/include/soc/{soc,soc_ulp,rtc_cntl_reg,rtc_io_reg,sens_reg}.h
96+
97+
2. Using the defines database during preprocessing
98+
99+
The preprocessor will automatically use a defines database, when using the
100+
`preprocess.preprocess` convenience function, even when the database does
101+
not exist (an absent database is treated like an empty database, and care
102+
is taken not to create an empty database file, cluttering up the filesystem,
103+
when not needed).
104+
105+
If you do not want the preprocessor use use a DefinesDB, pass `False` to
106+
the `use_defines_db` argument of the `preprocess` convenience function,
107+
or instantiate the `Preprocessor` class directly, without passing it a
108+
DefinesDB instance via `use_db`.
109+
110+
Design choices
111+
--------------
112+
113+
The preprocessor does not support:
114+
115+
1. Function style macros such as :code:`#define f(a,b) (a+b)`
116+
117+
This is not important, because there are only few RTC macros that need
118+
to be supported and they are simply implemented as Python functions.
119+
120+
Since the preprocessor will understand ``#define`` directives directly in the
121+
assembler source file, include mechanisms are not needed in some cases
122+
(simply copying the needed ``#define`` statements from include files into the
123+
assembler source will work).
124+
125+
2. ``#include`` directives
126+
127+
The preprocessor does not currently follow ``#include`` directives. To
128+
limit space requirements (both in memory and on the filesystem), the
129+
preprocessor relies on a database of defines (key/value pairs). This
130+
database should be populated before using the preprocessor, by using the
131+
``esp32_ulp.parse_to_db`` tool (see section above), which parses include
132+
files for identifiers defined therein.
133+
134+
3. Preserving comments
135+
136+
The assumption is that the output will almost always go into the
137+
assembler directly, so preserving comments is not very useful and
138+
would add a lot of complexity.

0 commit comments

Comments
 (0)