blob: 87e2a080ed945e7ddd27303dc50b8a81eefa5100 [file] [log] [blame]
.. _module-pw_emu-guide:
============
How-to guide
============
.. pigweed-module-subpage::
:name: pw_emu
:tagline: Emulators frontend
This guide shows you how to do common ``pw_emu`` tasks.
------------------------
Set up emulation targets
------------------------
An emulator target can be defined in the top level ``pigweed.json`` under
``pw:pw_emu:target`` or defined in a separate json file under ``targets`` and
then included in the top level ``pigweed.json`` via ``pw:pw_emu:target_files``.
For example, for the following layout:
.. code-block::
├── pigweed.json
└── pw_emu
└── qemu-lm3s6965evb.json
the following can be added to the ``pigweed.json`` file:
.. code-block::
{
"pw": {
"pw_emu": {
"target_files": [
"pw_emu/qemu-lm3s6965evb.json"
]
}
}
}
The following subsections presents examples of how to define new emulation
targets for QEMU and renode. For detailed emulation target configuration please
see :ref:`module-pw_emu-config`.
QEMU targets
============
When defining a QEMU emulation target the following keys have to be defined
under ``<target>:qemu``:
* ``executable``: 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
Here is an example that defines the ``qemu-lm3s6965evb`` target as configuration
fragment:
.. literalinclude:: qemu-lm3s6965evb.json
Since this is an ARM machine note that 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 and it should match a valid
chardev id for the particular QEMU machine.
This target 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 the
``serial0`` id. The default type of the channel is used, which is TCP and which is
supported by all platforms. The user can change the type by adding a ``type`` key
set to the desired type (e.g. ``pty``)
renode targets
==============
The following configuration fragment defines a target that uses renode:
.. literalinclude:: renode-stm32f4_discovery.json
Note that ``machine`` is used to identify which renode script to use for the
machine definitions and ``terminals`` to define which UART devices to expose to
the host.
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. The
configuration defines a ``serial0`` channel as the serial port identified as
``sysbus.usart1`` in the renode machine definition script.
-------------------
Run target binaries
-------------------
To quickly run target binaries on the host using an emulation target the ``pw
emu run`` command can be used. It will start an emulator instance, connect to a
(given) serial port and load and run the given binary.
.. code-block::
$ 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
---------
Debugging target binaries can be done using the ``pw emu gdb`` command. It
requires a running emulator instance which can be started with the ``pw emu
start`` command.
In the following example we are going to debug the ``status_test`` from the
``pw_status`` module compiled for :ref:`target-lm3s6965evb-qemu`. First we are
going to start an emulator instance using the ``qemu-lm3s6965evb`` emulator
target and load the test file:
.. code-block::
$ pw emu start qemu-lm3s6965evb \
--file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
Next, we will start a gdb session connected to the emulator instance:
.. code-block::
$ 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 gdb commands can be used to debug the program.
Once the debugging session is over the emulator instance should be stopped:
.. code-block::
$ pw emu stop
--------------
Boot debugging
--------------
To debug bootstraping code the ``-p`` option can be used when the emulator is
started:
.. code-block::
$ pw emu start -p qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
That will load the given program but the emulator will not start executing. Next, we can start a debugging session using the ``pw emu gdb`` commands:
.. code-block::
$ 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)
Note that the program is stopped at the ``pw_boot_Entry`` function. From here
you can add breakpoints or step through the program with gdb commands.
------------------------
Using multiple instances
------------------------
To use multiple emulator instances the ``--instance <instance-id>`` option can
be used. The default ``pw emu`` instance id is ``default``.
.. note::
Internally each emulator instance is identified by a working directory. pw
emu's working directory is ``$PROJECT_ROOT/.pw_emu/<instance-id>``
As an example, we will try to start the same emulator instance twice:
.. code-block::
$ 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
Note that the second command failed because the default emulator instance is
already running. To start another emulator instance we will use the
``--instance`` or ``-i`` option:
.. code-block::
$ pw emu -i my-instance start qemu-lm3s6965evb --file out/lm3s6965evb_qemu_gcc_size_optimized/obj/pw_status/test/status_test
Note that no error was triggered this time. Finally, lets stop both emulator
instances:
.. code-block::
$ pw emu stop -i my-instance
$ pw emu stop
-------------------------
Adding new emulator types
-------------------------
The pw_emu module 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:: python
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`` is using the Pigweed
configuration file to determine the connector and launcher classes, under the
following keys: ``pw_emu:emulators:<emulator_name>:connector`` and
``pw_emu:emulators:<emulator_name>:connector``. 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` and
:py:class:`pw_emu.core.Launcher._get_connector`.
There are several abstract methods that need to be implement for the connector,
like :py:meth:`pw_emu.core.Connector.reset` or
: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 abstract methods that
need to be implemented.