How-to guide#
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:
├── pigweed.json
└── pw_emu
└── qemu-lm3s6965evb.json
the following can be added to the pigweed.json
file:
{
"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 Configuration.
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; seeqemu-system-<arch> -machine help
for a list of supported machines names
Here is an example that defines the qemu-lm3s6965evb
target as configuration
fragment:
{
"targets": {
"qemu-lm3s6965evb": {
"gdb": [
"arm-none-eabi-gdb"
],
"qemu": {
"executable": "qemu-system-arm",
"machine": "lm3s6965evb",
"channels": {
"chardevs": {
"serial0": {
"id": "serial0"
}
}
}
}
}
}
}
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
and is compatible with the 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:
{
"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"
}
}
}
}
}
}
}
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 and is
compatible with the 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.
$ 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 lm3s6965evb-qemu. First we are
going to start an emulator instance using the qemu-lm3s6965evb
emulator
target and load the test file:
$ 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:
$ 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:
$ pw emu stop
Boot debugging#
To debug bootstraping code the -p
option can be used when the emulator is
started:
$ 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:
$ 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:
$ 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:
$ 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:
$ 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 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
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:
{
"pw": {
"pw_emu": {
"emulators": [
"myemu": {
"launcher": "mypkg.mymod.mylauncher",
"connector": "mypkg.mymod.myconnector",
}
]
}
}
}
The pw_emu.core.Launcher
implementation must implement the following
methods: pw_emu.core.Launcher._pre_start()
,
pw_emu.core.Launcher._post_start
and
pw_emu.core.Launcher._get_connector
.
There are several abstract methods that need to be implement for the connector,
like pw_emu.core.Connector.reset()
or
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 abstract methods that
need to be implemented.