blob: 68f2f500c77977c9120134efcb5610aadadf5e75 [file] [log] [blame]
.. _module-pw_emu-guide:
====================
Get started & guides
====================
.. pigweed-module-subpage::
:name: pw_emu
.. _module-pw_emu-get-started:
-----------
Get started
-----------
.. tab-set::
.. tab-item:: Bazel
``pw_emu`` is currently only supported for GN-based projects.
.. tab-item:: GN
Include the desired emulator target files :ref:`pigweed.json
<seed-0101>`. For example:
.. code-block:: json
"pw_emu": {
"target_files": [
"pw_emu/qemu-lm3s6965evb.json",
"pw_emu/qemu-stm32vldiscovery.json",
"pw_emu/qemu-netduinoplus2.json",
"renode-stm32f4_discovery.json"
]
}
See :ref:`module-pw_emu-config-fragments` for examples of target files.
Build a target and then use ``pw emu run`` to run the target binaries on
the host. Continuing with the example:
.. code-block:: console
ninja -C out qemu_gcc
pw emu run --args=-no-reboot qemu-lm3s6965evb \
out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_snapshot/test/cpp_compile_test
.. tab-item:: CMake
``pw_emu`` is not currently supported for CMake-based projects.
.. tab-item:: Zephyr
``pw_emu`` is not currently supported for Zephyr-based projects.
.. tab-item:: Android host
Create a :ref:`pigweed.json <seed-0101>` file and include the
desired emulator targets.
Build the ``pw emu`` standalone Android Python executable:
.. code-block:: console
m pw_emu_py
Run ``pw emu``:
.. code-block:: console
pw_emu_py -c <path to pigweed.json> start <emulator target>
------------------------
Set up emulation targets
------------------------
An emulator target can be defined directly in the ``pw.pw_emu`` namespace of
:ref:`pigweed.json <seed-0101>`, like this:
.. code-block::
{
"pw": {
"pw_emu": {
"targets": {
"qemu-lm3s6965evb": {
"...": "..."
}
}
}
}
}
Or it can be defined elsewhere and then imported into ``pigweed.json``, like
this:
.. code-block::
{
"pw": {
"pw_emu": {
"target_files": [
"qemu-lm3s6965evb.json"
]
}
}
}
Relative paths are interpreted relative to the root of your project directory.
You can configure default options at the emulator level and then override
those options at the target or channel level. See :ref:`module-pw_emu-config`
for details.
QEMU targets
============
When defining a QEMU emulation target the following keys must be defined
under ``<target>.qemu`` (where ``<target>`` is a placeholder for a real target
name):
* ``executable`` - The name of the QEMU executable, e.g. ``qemu-system-arm``,
``qemusystem-riscv64``, etc.
* ``machine`` - The QEMU machine name. See ``qemu-system-<arch> -machine help``
for a list of supported machines names.
The following example is a :ref:`config fragment <module-pw_emu-config-fragments>`
for a target that runs on QEMU:
.. literalinclude:: qemu-lm3s6965evb.json
.. note::
Since this is an Arm machine the QEMU executable is defined as
``qemu-system-arm``.
QEMU chardevs can be exposed as host channels under
``<target>.qemu.channels.chardevs.<chan-name>`` where ``<chan-name>`` is
the name that the channel can be accessed with (e.g. ``pw emu term
<chan-name>``). The ``id`` option is mandatory; it should match a valid
chardev ``id`` for this particular QEMU machine.
The example target above emulates a `Stellaris EEVB
<https://www.ti.com/product/LM3S6965>`_ and is compatible with the
:ref:`target-lm3s6965evb-qemu` Pigweed target. The configuration defines a
``serial0`` channel to be the QEMU ``chardev`` with an ``id`` of ``serial0``.
The default channel type (``tcp``) is used, which is supported by all platforms.
You can change the type by adding a ``type`` key set to the desired type, e.g.
``pty``.
Renode targets
==============
The following example is a :ref:`config fragment <module-pw_emu-config-fragments>`
for a target that runs on Renode:
.. literalinclude:: renode-stm32f4_discovery.json
This target emulates the `ST 32F429I Discovery Kit
<https://www.st.com/en/evaluation-tools/32f429idiscovery.html>`_ and is
compatible with the :ref:`target-stm32f429i-disc1` Pigweed target. ``machine``
identifies which Renode script to use for the machine definitions. ``terminals``
defines which UART devices to expose to the host. ``serial0`` exposes the serial
port identified as ``sysbus.usart1`` in the Renode machine script.
-------------------
Run target binaries
-------------------
Use ``pw emu run`` to quickly run target binaries on the host. ``pw emu run``
starts an emulator instance, connects to a given serial port, and then loads
and runs the given binary.
.. code-block:: console
$ pw emu run --args=-no-reboot qemu-lm3s6965evb \
out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_snapshot/test/cpp_compile_test
--- Miniterm on serial0 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
INF [==========] Running all tests.
INF [ RUN ] Status.CompileTest
INF [ OK ] Status.CompileTest
INF [==========] Done running all tests.
INF [ PASSED ] 1 test(s).
--- exit ---
.. note::
The ``-no-reboot`` option is passed directly to QEMU and instructs the
emulator to quit instead of rebooting.
---------
Debugging
---------
Use ``pw emu gdb`` to debug target binaries.
.. note::
You always need to run an emulator instance (``pw emu start``) before
starting a debug session.
In the following example, ``status_test`` from ``pw_status`` is debugged. The
binary was compiled for :ref:`target-lm3s6965evb-qemu`. First an emulator
instance is started using the ``qemu-lm3s6965evb`` emulation target definition
and then the test file is loaded:
.. code-block:: console
$ pw emu start qemu-lm3s6965evb \
--file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
Next, a ``gdb`` session is started and connected to the emulator instance:
.. code-block:: console
$ pw emu gdb -e out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
GNU gdb (Arm GNU Toolchain 12.2.MPACBTI-Rel1 ...
...
Reading symbols from out/stm32f429i_disc1_debug/obj/pw_status/test/status_test.elf...
Remote debugging using ::1:32979
pw::sys_io::WriteByte (b=(unknown: 0x20)) at pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc:117
117 uart0.data_register = static_cast<uint32_t>(b);
(gdb) bt
#0 pw::sys_io::WriteByte (b=(unknown: 0x20)) at pw_sys_io_baremetal_lm3s6965evb/sys_io_baremetal.cc:117
#1 0x00002f6a in pw::sys_io::WriteBytes (src=...) at pw_span/public/pw_span/internal/span_impl.h:408
#2 0x00002eca in pw::sys_io::WriteLine (s=...) at pw_span/public/pw_span/internal/span_impl.h:264
#3 0x00002f92 in operator() (log=..., __closure=0x0 <vector_table>) at pw_log_basic/log_basic.cc:87
#4 _FUN () at pw_log_basic/log_basic.cc:89
#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,
function_name=0x6e68 "TestCaseStart", message=0x6e55 "[ RUN ] %s.%s") at pw_log_basic/log_basic.cc:155
#6 0x00002b0a in pw::unit_test::LoggingEventHandler::TestCaseStart (this=<optimized out>, test_case=...) at pw_unit_test/logging_event_handler.cc:95
#7 0x00000f54 in pw::unit_test::internal::Framework::CreateAndRunTest<pw::(anonymous namespace)::Status_Strings_Test> (test_info=...)
at pw_unit_test/public/pw_unit_test/internal/framework.h:266
#8 0x0000254a in pw::unit_test::internal::TestInfo::run (this=0x20000280 <_pw_unit_test_Info_Status_Strings>)
at pw_unit_test/public/pw_unit_test/internal/framework.h:413
#9 pw::unit_test::internal::Framework::RunAllTests (this=0x20000350 <pw::unit_test::internal::Framework::framework_>) at pw_unit_test/framework.cc:64
#10 0x000022b0 in main (argc=<optimized out>, argv=<optimized out>) at pw_unit_test/public/pw_unit_test/internal/framework.h:218
At this point you can debug the program with ``gdb`` commands.
To stop the debugging session:
.. code-block:: console
$ pw emu stop
--------------
Boot debugging
--------------
Use the ``-p`` or ``--pause`` option when starting an emulator to debug
bootstrapping code:
.. code-block:: console
$ pw emu start -p qemu-lm3s6965evb \
--file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
The given program loads but the emulator doesn't start executing. Next, start
a debugging session with ``pw emu gdb``:
.. code-block:: console
$ pw emu gdb -e out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
GNU gdb (Arm GNU Toolchain 12.2.MPACBTI-Rel1 ...
...
Reading symbols from out/lm3s6965evb_qemu_gcc_size_optimized//obj/pw_status/test/status_test...
Remote debugging using ::1:38723
pw_boot_Entry () at pw_boot_cortex_m/core_init.c:122
122 asm volatile("cpsid i");
(gdb)
The program stops at the :ref:`pw_boot_Entry() <module-pw_boot>` function. From
here you can add breakpoints or step through the program with ``gdb`` commands.
-------------------------------
Run multiple emulator instances
-------------------------------
Use the ``-i`` or ``--instance`` option to run multiple emulator instances.
.. note::
Internally each emulator instance is identified by a working directory. The
working directory for ``pw_emu`` is ``$PROJECT_ROOT/.pw_emu/<instance-id>``.
The next example attempts to start two emulators at the same time:
.. code-block:: console
$ pw emu start qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
$ pw emu start qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
pigweed/.pw_emu/default: emulator already started
This fails because ``pw emu`` attempts to assign the same default instance
ID to each instance. The default ID is ``default``. To fix this, assign the
second instance a custom ID:
.. code-block:: console
$ pw emu start qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
$ pw emu -i instance2 start qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
To stop both emulator instances:
.. code-block:: console
$ pw emu stop
$ pw emu stop -i instance2
-------------------------
Adding new emulator types
-------------------------
``pw_emu`` can be extended to support new emulator types by providing
implementations for :py:class:`pw_emu.core.Launcher` and
:py:class:`pw_emu.core.Connector` in a dedicated ``pw_emu`` Python module
(e.g. :py:mod:`pw_emu.myemu`) or in an external Python module.
Internal ``pw_emu`` modules must register the connector and launcher
classes by updating :py:obj:`pw_emu.pigweed_emulators.pigweed_emulators`. For
example, the QEMU implementation sets the following values:
.. code-block:: py
pigweed_emulators: dict[str, dict[str, str]] = {
...
'qemu': {
'connector': 'pw_emu.qemu.QemuConnector',
'launcher': 'pw_emu.qemu.QemuLauncher',
},
...
}
For external emulator frontend modules, ``pw_emu`` uses
:ref:`pigweed.json <seed-0101>` to determine the connector and launcher
classes under ``pw_emu.emulators.<emulator-name>.connector`` and
``pw_emu.emulators:<emulator-name>.launcher``.
Configuration example:
.. code-block::
{
"pw": {
"pw_emu": {
"emulators": [
"myemu": {
"launcher": "mypkg.mymod.mylauncher",
"connector": "mypkg.mymod.myconnector",
}
]
}
}
}
The :py:class:`pw_emu.core.Launcher` implementation must implement the following
methods:
* :py:meth:`pw_emu.core.Launcher._pre_start`
* :py:class:`pw_emu.core.Launcher._post_start`
* :py:class:`pw_emu.core.Launcher._get_connector`
There are several abstract methods that need to be implemented for the
connector, like :py:meth:`pw_emu.core.Connector.reset` and
:py:meth:`pw_emu.core.Connector.cont`. These are typically implemented using
internal channels and :py:class:`pw_emu.core.Connector.get_channel_stream`. See
:py:class:`pw_emu.core.Connector` for a complete list of the abstract methods
that need to be implemented.
----------------
More pw_emu docs
----------------
.. include:: docs.rst
:start-after: .. pw_emu-nav-start
:end-before: .. pw_emu-nav-end