Get started & guides#
pw_emu: Flexible emulators frontend
Get started#
pw_emu
is currently only supported for GN-based projects.
Include the desired emulator target files pigweed.json. For example:
"pw_emu": {
"target_files": [
"pw_emu/qemu-lm3s6965evb.json",
"pw_emu/qemu-stm32vldiscovery.json",
"pw_emu/qemu-netduinoplus2.json",
"renode-stm32f4_discovery.json"
]
}
See Configuration 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:
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
pw_emu
is not currently supported for CMake-based projects.
pw_emu
is not currently supported for Zephyr-based projects.
Create a pigweed.json file and include the desired emulator targets.
Build the pw emu
standalone Android Python executable:
m pw_emu_py
Run pw emu
:
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
pigweed.json, like this:
{
"pw": {
"pw_emu": {
"targets": {
"qemu-lm3s6965evb": {
"...": "..."
}
}
}
}
}
Or it can be defined elsewhere and then imported into pigweed.json
, like
this:
{
"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 Configuration 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. Seeqemu-system-<arch> -machine help
for a list of supported machines names.
The following example is a config fragment for a target that runs on QEMU:
{
"targets": {
"qemu-lm3s6965evb": {
"gdb": [
"arm-none-eabi-gdb"
],
"qemu": {
"executable": "qemu-system-arm",
"machine": "lm3s6965evb",
"channels": {
"chardevs": {
"serial0": {
"id": "serial0"
}
}
}
}
}
}
}
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 and is compatible with the
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 config fragment for a target that runs on Renode:
{
"targets": {
"renode-stm32f4_discovery": {
"gdb": [
"arm-none-eabi-gdb"
],
"renode": {
"executable": "renode",
"machine": "platforms/boards/stm32f4_discovery-kit.repl",
"channels": {
"terminals": {
"serial0": {
"device-path": "sysbus.usart1"
}
}
}
}
}
}
}
This target emulates the ST 32F429I Discovery Kit and is
compatible with the 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.
$ 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 lm3s6965evb-qemu. First an emulator
instance is started using the qemu-lm3s6965evb
emulation target definition
and then the test file is loaded:
$ 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:
$ 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:
$ pw emu stop
Boot debugging#
Use the -p
or --pause
option when starting an emulator to debug
bootstrapping code:
$ 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
:
$ 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 pw_boot_Entry() 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:
$ 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:
$ 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:
$ 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 pw_emu.core.Launcher
and
pw_emu.core.Connector
in a dedicated pw_emu
Python module
(e.g. pw_emu.myemu
) or in an external Python module.
Internal pw_emu
modules must register the connector and launcher
classes by updating pw_emu.pigweed_emulators.pigweed_emulators
. For
example, the QEMU implementation sets the following values:
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
pigweed.json to determine the connector and launcher
classes under pw_emu.emulators.<emulator-name>.connector
and
pw_emu.emulators:<emulator-name>.launcher
.
Configuration example:
{
"pw": {
"pw_emu": {
"emulators": [
"myemu": {
"launcher": "mypkg.mymod.mylauncher",
"connector": "mypkg.mymod.myconnector",
}
]
}
}
}
The pw_emu.core.Launcher
implementation must implement the following
methods:
There are several abstract methods that need to be implemented for the
connector, like pw_emu.core.Connector.reset()
and
pw_emu.core.Connector.cont()
. These are typically implemented using
internal channels and pw_emu.core.Connector.get_channel_stream
. See
pw_emu.core.Connector
for a complete list of the abstract methods
that need to be implemented.