xref: /aosp_15_r20/external/pigweed/pw_emu/guide.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1.. _module-pw_emu-guide:
2
3====================
4Get started & guides
5====================
6.. pigweed-module-subpage::
7   :name: pw_emu
8
9.. _module-pw_emu-get-started:
10
11-----------
12Get started
13-----------
14.. tab-set::
15
16   .. tab-item:: Bazel
17
18      ``pw_emu`` is currently only supported for GN-based projects.
19
20   .. tab-item:: GN
21
22      Include the desired emulator target files :ref:`pigweed.json
23      <seed-0101>`. For example:
24
25      .. code-block:: json
26
27         "pw_emu": {
28            "target_files": [
29              "pw_emu/qemu-lm3s6965evb.json",
30              "pw_emu/qemu-stm32vldiscovery.json",
31              "pw_emu/qemu-netduinoplus2.json",
32              "renode-stm32f4_discovery.json"
33            ]
34          }
35
36      See :ref:`module-pw_emu-config-fragments` for examples of target files.
37
38      Build a target and then use ``pw emu run`` to run the target binaries on
39      the host. Continuing with the example:
40
41      .. code-block:: console
42
43         ninja -C out qemu_gcc
44         pw emu run --args=-no-reboot qemu-lm3s6965evb \
45             out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_snapshot/test/cpp_compile_test
46
47   .. tab-item:: CMake
48
49      ``pw_emu`` is not currently supported for CMake-based projects.
50
51   .. tab-item:: Zephyr
52
53      ``pw_emu`` is not currently supported for Zephyr-based projects.
54
55   .. tab-item:: Android host
56
57      Create a :ref:`pigweed.json <seed-0101>` file and include the
58      desired emulator targets.
59
60      Build the ``pw emu`` standalone Android Python executable:
61
62      .. code-block:: console
63
64         m pw_emu_py
65
66      Run ``pw emu``:
67
68      .. code-block:: console
69
70         pw_emu_py -c <path to pigweed.json> start <emulator target>
71
72------------------------
73Set up emulation targets
74------------------------
75An emulator target can be defined directly in the ``pw.pw_emu`` namespace of
76:ref:`pigweed.json <seed-0101>`, like this:
77
78.. code-block::
79
80   {
81     "pw": {
82       "pw_emu": {
83         "targets": {
84           "qemu-lm3s6965evb": {
85             "...": "..."
86           }
87         }
88       }
89     }
90   }
91
92Or it can be defined elsewhere and then imported into ``pigweed.json``, like
93this:
94
95.. code-block::
96
97   {
98     "pw": {
99       "pw_emu": {
100         "target_files": [
101           "qemu-lm3s6965evb.json"
102         ]
103       }
104     }
105   }
106
107Relative paths are interpreted relative to the root of your project directory.
108
109You can configure default options at the emulator level and then override
110those options at the target or channel level. See :ref:`module-pw_emu-config`
111for details.
112
113QEMU targets
114============
115When defining a QEMU emulation target the following keys must be defined
116under ``<target>.qemu`` (where ``<target>`` is a placeholder for a real target
117name):
118
119* ``executable`` - The name of the QEMU executable, e.g. ``qemu-system-arm``,
120  ``qemusystem-riscv64``, etc.
121* ``machine`` - The QEMU machine name. See ``qemu-system-<arch> -machine help``
122  for a list of supported machines names.
123
124The following example is a :ref:`config fragment <module-pw_emu-config-fragments>`
125for a target that runs on QEMU:
126
127.. literalinclude:: qemu-lm3s6965evb.json
128
129.. note::
130
131   Since this is an Arm machine the QEMU executable is defined as
132   ``qemu-system-arm``.
133
134QEMU chardevs can be exposed as host channels under
135``<target>.qemu.channels.chardevs.<chan-name>`` where ``<chan-name>`` is
136the name that the channel can be accessed with (e.g. ``pw emu term
137<chan-name>``). The ``id`` option is mandatory; it should match a valid
138chardev ``id`` for this particular QEMU machine.
139
140The example target above emulates a `Stellaris EEVB
141<https://www.ti.com/product/LM3S6965>`_ and is compatible with the
142:ref:`target-lm3s6965evb-qemu` Pigweed target. The configuration defines a
143``serial0`` channel to be the QEMU ``chardev`` with an ``id`` of ``serial0``.
144The default channel type (``tcp``) is used, which is supported by all platforms.
145You can change the type by adding a ``type`` key set to the desired type, e.g.
146``pty``.
147
148Renode targets
149==============
150The following example is a :ref:`config fragment <module-pw_emu-config-fragments>`
151for a target that runs on Renode:
152
153.. literalinclude:: renode-stm32f4_discovery.json
154
155This target emulates the `ST 32F429I Discovery Kit
156<https://www.st.com/en/evaluation-tools/32f429idiscovery.html>`_ and is
157compatible with the :ref:`target-stm32f429i-disc1` Pigweed target.  ``machine``
158identifies which Renode script to use for the machine definitions. ``terminals``
159defines which UART devices to expose to the host. ``serial0`` exposes the serial
160port identified as ``sysbus.usart1`` in the Renode machine script.
161
162-------------------
163Run target binaries
164-------------------
165Use ``pw emu run`` to quickly run target binaries on the host. ``pw emu run``
166starts an emulator instance, connects to a given serial port, and then loads
167and runs the given binary.
168
169.. code-block:: console
170
171   $ pw emu run --args=-no-reboot qemu-lm3s6965evb \
172         out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_snapshot/test/cpp_compile_test
173
174   --- Miniterm on serial0 ---
175   --- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
176   INF  [==========] Running all tests.
177   INF  [ RUN      ] Status.CompileTest
178   INF  [       OK ] Status.CompileTest
179   INF  [==========] Done running all tests.
180   INF  [  PASSED  ] 1 test(s).
181   --- exit ---
182
183.. note::
184
185   The ``-no-reboot`` option is passed directly to QEMU and instructs the
186   emulator to quit instead of rebooting.
187
188---------
189Debugging
190---------
191Use ``pw emu gdb`` to debug target binaries.
192
193.. note::
194
195   You always need to run an emulator instance (``pw emu start``) before
196   starting a debug session.
197
198In the following example, ``status_test`` from ``pw_status`` is debugged. The
199binary was compiled for :ref:`target-lm3s6965evb-qemu`. First an emulator
200instance is started using the ``qemu-lm3s6965evb`` emulation target definition
201and then the test file is loaded:
202
203.. code-block:: console
204
205   $ pw emu start qemu-lm3s6965evb \
206       --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
207
208Next, a ``gdb`` session is started and connected to the emulator instance:
209
210.. code-block:: console
211
212   $ pw emu gdb -e out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
213   GNU gdb (Arm GNU Toolchain 12.2.MPACBTI-Rel1 ...
214   ...
215   Reading symbols from out/stm32f429i_disc1_debug/obj/pw_status/test/status_test.elf...
216   Remote debugging using ::1:32979
217   pw::sys_io::WriteByte (b=(unknown: 0x20)) at pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc:117
218   117    uart0.data_register = static_cast<uint32_t>(b);
219   (gdb) bt
220   #0  pw::sys_io::WriteByte (b=(unknown: 0x20)) at pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc:117
221   #1  0x00002f6a in pw::sys_io::WriteBytes (src=...) at pw_span/public/pw_span/internal/span_impl.h:408
222   #2  0x00002eca in pw::sys_io::WriteLine (s=...) at pw_span/public/pw_span/internal/span_impl.h:264
223   #3  0x00002f92 in operator() (log=..., __closure=0x0 <vector_table>) at pw_log_basic/log_basic.cc:87
224   #4  _FUN () at pw_log_basic/log_basic.cc:89
225   #5  0x00002fec in pw::log_basic::pw_Log (level=<optimized out>, flags=<optimized out>, module_name=<optimized out>, file_name=<optimized out>, line_number=95,
226    function_name=0x6e68 "TestCaseStart", message=0x6e55 "[ RUN      ] %s.%s") at pw_log_basic/log_basic.cc:155
227   #6  0x00002b0a in pw::unit_test::LoggingEventHandler::TestCaseStart (this=<optimized out>, test_case=...) at pw_unit_test/logging_event_handler.cc:95
228   #7  0x00000f54 in pw::unit_test::internal::Framework::CreateAndRunTest<pw::(anonymous namespace)::Status_Strings_Test> (test_info=...)
229    at pw_unit_test/public/pw_unit_test/internal/framework.h:266
230   #8  0x0000254a in pw::unit_test::internal::TestInfo::run (this=0x20000280 <_pw_unit_test_Info_Status_Strings>)
231    at pw_unit_test/public/pw_unit_test/internal/framework.h:413
232   #9  pw::unit_test::internal::Framework::RunAllTests (this=0x20000350 <pw::unit_test::internal::Framework::framework_>) at pw_unit_test/framework.cc:64
233   #10 0x000022b0 in main (argc=<optimized out>, argv=<optimized out>) at pw_unit_test/public/pw_unit_test/internal/framework.h:218
234
235At this point you can debug the program with ``gdb`` commands.
236
237To stop the debugging session:
238
239.. code-block:: console
240
241   $ pw emu stop
242
243--------------
244Boot debugging
245--------------
246Use the ``-p`` or ``--pause`` option when starting an emulator to debug
247bootstrapping code:
248
249.. code-block:: console
250
251   $ pw emu start -p qemu-lm3s6965evb \
252         --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
253
254The given program loads but the emulator doesn't start executing. Next, start
255a debugging session with ``pw emu gdb``:
256
257.. code-block:: console
258
259   $ pw emu gdb -e out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
260   GNU gdb (Arm GNU Toolchain 12.2.MPACBTI-Rel1 ...
261   ...
262   Reading symbols from out/lm3s6965evb_qemu_gcc_size_optimized//obj/pw_status/test/status_test...
263   Remote debugging using ::1:38723
264   pw_boot_Entry () at pw_boot_cortex_m/core_init.c:122
265   122    asm volatile("cpsid i");
266   (gdb)
267
268The program stops at the :ref:`pw_boot_Entry() <module-pw_boot>` function. From
269here you can add breakpoints or step through the program with ``gdb`` commands.
270
271-------------------------------
272Run multiple emulator instances
273-------------------------------
274Use the ``-i`` or ``--instance`` option to run multiple emulator instances.
275
276.. note::
277
278   Internally each emulator instance is identified by a working directory. The
279   working directory for ``pw_emu`` is ``$PROJECT_ROOT/.pw_emu/<instance-id>``.
280
281The next example attempts to start two emulators at the same time:
282
283.. code-block:: console
284
285   $ pw emu start qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
286   $ pw emu start qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
287   pigweed/.pw_emu/default: emulator already started
288
289
290This fails because ``pw emu`` attempts to assign the same default instance
291ID to each instance. The default ID is ``default``. To fix this, assign the
292second instance a custom ID:
293
294.. code-block:: console
295
296   $ pw emu start qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
297   $ pw emu -i instance2 start qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
298
299To stop both emulator instances:
300
301.. code-block:: console
302
303   $ pw emu stop
304   $ pw emu stop -i instance2
305
306-------------------------
307Adding new emulator types
308-------------------------
309``pw_emu`` can be extended to support new emulator types by providing
310implementations for :py:class:`pw_emu.core.Launcher` and
311:py:class:`pw_emu.core.Connector` in a dedicated ``pw_emu`` Python module
312(e.g. :py:mod:`pw_emu.myemu`) or in an external Python module.
313
314Internal ``pw_emu`` modules must register the connector and launcher
315classes by updating :py:obj:`pw_emu.pigweed_emulators.pigweed_emulators`. For
316example, the QEMU implementation sets the following values:
317
318.. code-block:: py
319
320   pigweed_emulators: dict[str, dict[str, str]] = {
321     ...
322     'qemu': {
323       'connector': 'pw_emu.qemu.QemuConnector',
324       'launcher': 'pw_emu.qemu.QemuLauncher',
325      },
326     ...
327   }
328
329For external emulator frontend modules, ``pw_emu`` uses
330:ref:`pigweed.json <seed-0101>` to determine the connector and launcher
331classes under ``pw_emu.emulators.<emulator-name>.connector`` and
332``pw_emu.emulators:<emulator-name>.launcher``.
333
334Configuration example:
335
336.. code-block::
337
338   {
339     "pw": {
340       "pw_emu": {
341         "emulators": [
342           "myemu": {
343             "launcher": "mypkg.mymod.mylauncher",
344             "connector": "mypkg.mymod.myconnector",
345           }
346         ]
347       }
348     }
349   }
350
351The :py:class:`pw_emu.core.Launcher` implementation must implement the following
352methods:
353
354* :py:meth:`pw_emu.core.Launcher._pre_start`
355* :py:class:`pw_emu.core.Launcher._post_start`
356* :py:class:`pw_emu.core.Launcher._get_connector`
357
358There are several abstract methods that need to be implemented for the
359connector, like :py:meth:`pw_emu.core.Connector.reset` and
360:py:meth:`pw_emu.core.Connector.cont`.  These are typically implemented using
361internal channels and :py:class:`pw_emu.core.Connector.get_channel_stream`. See
362:py:class:`pw_emu.core.Connector` for a complete list of the abstract methods
363that need to be implemented.
364
365----------------
366More pw_emu docs
367----------------
368.. include:: docs.rst
369   :start-after: .. pw_emu-nav-start
370   :end-before: .. pw_emu-nav-end
371