Skip to content

Commit 5be325a

Browse files
authored
Arm backend: Wire examples/arm/run.sh for cortex-m55+int8 e2e testing (#19073)
### Summary Adds cortex-m55+int8 target support to the public Arm runner scripts so the bundled-IO FVP test flow is reachable from examples/arm/run.sh instead of being inlined in .ci/scripts/test_cortex_m_e2e.sh. backends/arm/scripts/run_fvp.sh now accepts --bundle=<bpte> and, when the target matches cortex-m, launches the Corstone-300 FVP with semihosting enabled and the cortex-m test runner as the loaded ELF, then greps the FVP log for the Test_result: PASS/FAIL line emitted by the bundled-IO runner. examples/arm/run.sh detects cortex-m targets, force-disables --delegate (the cortex-m backend is an operator library, not a delegate), invokes backends/cortex_m/test/build_test_runner.sh for the shared semihosting runner, and delegates the FVP launch to run_fvp.sh. .ci/scripts/test_cortex_m_e2e.sh becomes a thin wrapper over run.sh so CI and user-facing docs exercise the same code path. The redundant build_test_runner.sh invocation in trunk.yml is removed since run.sh now calls it internally. This change was authored with Claude (claude-opus-4-7[1m]). ### Test plan ``` # Step 1 — Primary e2e via run.sh (expect Test_result: PASS and exit 0): examples/arm/run.sh --model_name=add --target=cortex-m55+int8 --quantize --bundleio # Step 2 — Second model (mv2): examples/arm/run.sh --model_name=mv2 --target=cortex-m55+int8 --quantize --bundleio # Step 3 — CI-wrapper parity (test_cortex_m_e2e.sh): bash .ci/scripts/test_cortex_m_e2e.sh add # Step 4 — Negative path (missing --bundleio): Expect exit 1 with error examples/arm/run.sh --model_name=add --target=cortex-m55+int8 --quantize # Step 5 — Regression guard for the edited run_fvp.sh ethos-u55 path (expect model to run on FVP and exit 0): examples/arm/run.sh --model_name=add --target=ethos-u55-128 --quantize ``` cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell
1 parent 524f037 commit 5be325a

5 files changed

Lines changed: 94 additions & 75 deletions

File tree

.ci/scripts/test_cortex_m_e2e.sh

Lines changed: 12 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,76 +6,20 @@
66
# This source code is licensed under the BSD-style license found in the
77
# LICENSE file in the root directory of this source tree.
88

9-
# End-to-end test for Cortex-M backend: export a model via aot_arm_compiler
10-
# with cortex-m55+int8 target, then run the .bpte on Corstone-300 FVP.
11-
#
12-
# Usage: bash .ci/scripts/test_cortex_m_e2e.sh <model_name>
13-
# Example: bash .ci/scripts/test_cortex_m_e2e.sh mv2
9+
# CI wrapper: export a model for the Cortex-M backend and run it on the
10+
# Corstone-300 FVP via examples/arm/run.sh. The real work (export, runner
11+
# build, FVP launch, Test_result: PASS/FAIL check) is done by run.sh and
12+
# the run_fvp.sh it invokes.
1413

15-
set -eux
14+
set -eu
1615

1716
MODEL=$1
18-
mkdir -p "./cortex_m_e2e/${MODEL}"
19-
WORK_DIR=$(realpath "./cortex_m_e2e/${MODEL}")
17+
script_dir=$(realpath "$(dirname "${BASH_SOURCE[0]}")")
18+
et_root_dir=$(realpath "${script_dir}/../..")
2019

21-
echo "=== Exporting ${MODEL} with cortex-m55+int8 ==="
22-
python -m backends.arm.scripts.aot_arm_compiler \
23-
-m "${MODEL}" \
20+
# Quantization is the default for the cortex-m55+int8 target; run.sh's
21+
# arg parser only recognizes --no_quantize, so we omit any explicit flag.
22+
bash "${et_root_dir}/examples/arm/run.sh" \
23+
--model_name="${MODEL}" \
2424
--target=cortex-m55+int8 \
25-
--quantize \
26-
--bundleio \
27-
--intermediates="${WORK_DIR}/intermediates" \
28-
--output="${WORK_DIR}/${MODEL}.bpte"
29-
30-
BPTE="${WORK_DIR}/${MODEL}.bpte"
31-
test -f "${BPTE}" || { echo "FAIL: ${BPTE} not produced"; exit 1; }
32-
echo "=== Exported ${BPTE} ($(stat --printf='%s' "${BPTE}") bytes) ==="
33-
34-
ELF="arm_test/arm_semihosting_executor_runner_corstone-300/arm_executor_runner"
35-
test -f "${ELF}" || { echo "FAIL: executor runner not found at ${ELF}"; exit 1; }
36-
37-
LOG_FILE=$(mktemp)
38-
39-
# Create a tiny dummy input file — the runner requires -i but BundleIO
40-
# ignores it and uses the embedded test inputs instead.
41-
dd if=/dev/zero of="${WORK_DIR}/dummy.bin" bs=4 count=1 2>/dev/null
42-
43-
echo "=== Running ${MODEL} on Corstone-300 FVP ==="
44-
FVP_Corstone_SSE-300_Ethos-U55 \
45-
-C ethosu.num_macs=128 \
46-
-C mps3_board.visualisation.disable-visualisation=1 \
47-
-C mps3_board.telnetterminal0.start_telnet=0 \
48-
-C mps3_board.uart0.out_file='-' \
49-
-C mps3_board.uart0.shutdown_on_eot=1 \
50-
-C cpu0.semihosting-enable=1 \
51-
-C cpu0.semihosting-stack_base=0 \
52-
-C cpu0.semihosting-heap_limit=0 \
53-
-C "cpu0.semihosting-cwd=${WORK_DIR}" \
54-
-C "ethosu.extra_args='--fast'" \
55-
-C "cpu0.semihosting-cmd_line='executor_runner -m ${MODEL}.bpte -i dummy.bin -o out'" \
56-
-a "${ELF}" \
57-
--timelimit 300 2>&1 | tee "${LOG_FILE}" || true
58-
59-
echo "=== Checking FVP output ==="
60-
61-
if grep -q "Test_result: PASS" "${LOG_FILE}"; then
62-
echo "=== SUCCESS: ${MODEL} e2e BundleIO test PASSED on FVP ==="
63-
rm "${LOG_FILE}"
64-
exit 0
65-
fi
66-
67-
if grep -q "Test_result: FAIL" "${LOG_FILE}"; then
68-
echo "FAIL: ${MODEL} BundleIO output mismatch"
69-
rm "${LOG_FILE}"
70-
exit 1
71-
fi
72-
73-
if grep -qE "(^[EF][: ].*$)|(^.*Hard fault.*$)|(^.*Assertion.*$)" "${LOG_FILE}"; then
74-
echo "FAIL: ${MODEL} FVP run hit a fatal error"
75-
rm "${LOG_FILE}"
76-
exit 1
77-
fi
78-
79-
echo "FAIL: ${MODEL} no BundleIO test result found in FVP output"
80-
rm "${LOG_FILE}"
81-
exit 1
25+
--bundleio

.github/workflows/_test_cortex_m_e2e.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,5 @@ jobs:
4343
.ci/scripts/setup-arm-baremetal-tools.sh
4444
source examples/arm/arm-scratch/setup_path.sh
4545
46-
# Build cortex-m test runner with bundled IO support
47-
backends/cortex_m/test/build_test_runner.sh
48-
49-
# Export model and run on FVP
46+
# Export and run model on FVP (run.sh internally builds the test runner).
5047
bash .ci/scripts/test_cortex_m_e2e.sh ${{ matrix.model }}

backends/arm/scripts/run_fvp.sh

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ _setup_msg="please refer to ${et_root_dir}/examples/arm/setup.sh to properly ins
1919

2020
elf_file=""
2121
data_file=""
22+
bundle_file=""
2223
target="ethos-u55-128"
2324
timeout="600"
2425
etrecord_file=""
@@ -29,6 +30,7 @@ help() {
2930
echo "Options:"
3031
echo " --elf=<ELF_FILE> elf file to run"
3132
echo " --data=<FILE>@<ADDRESS> Place a file in memory at this address, useful to emulate a PTE flashed into memory instead as part of the code."
33+
echo " --bundle=<BPTE_FILE> Bundled program (.bpte) to load via semihosting. Required for cortex-m targets; the FVP launches a semihosting executor_runner that reads the bundle from the host filesystem and checks the embedded reference outputs."
3234
echo " --target=<TARGET> Target to build and run for Default: ${target}"
3335
echo " --timeout=<TIME_IN_SEC> Maximum target runtime, used to detect hanging, might need to be higer on large models Default: ${timeout}"
3436
echo " --etrecord=<FILE> If ETDump is used you can supply a ETRecord file matching the PTE"
@@ -41,6 +43,7 @@ for arg in "$@"; do
4143
-h|--help) help ;;
4244
--elf=*) elf_file="${arg#*=}";;
4345
--data=*) data_file="--data ${arg#*=}";;
46+
--bundle=*) bundle_file="${arg#*=}";;
4447
--target=*) target="${arg#*=}";;
4548
--timeout=*) timeout="${arg#*=}";;
4649
--etrecord=*) etrecord_file="${arg#*=}";;
@@ -52,7 +55,9 @@ done
5255

5356
elf_file=$(realpath ${elf_file})
5457

55-
if [[ ${target} == *"ethos-u55"* ]]; then
58+
# cortex-m55 is the only Cortex-M CPU on the Corstone-300 board today;
59+
# cortex-m85 lives on Corstone-320, so it falls through to the SSE-320 FVP.
60+
if [[ ${target} == *"ethos-u55"* || ${target} == cortex-m55* ]]; then
5661
fvp_model=FVP_Corstone_SSE-300_Ethos-U55
5762
else
5863
fvp_model=FVP_Corstone_SSE-320
@@ -71,7 +76,12 @@ hash ${fvp_model} \
7176

7277

7378
[[ ! -f $elf_file ]] && { echo "[${BASH_SOURCE[0]}]: Unable to find executor_runner elf: ${elf_file}"; exit 1; }
74-
num_macs=$(echo ${target} | cut -d - -f 3)
79+
if [[ ${target} == cortex-m* ]]; then
80+
# Cortex-M CPU-only; the NPU is unused but the FVP still needs a value.
81+
num_macs=128
82+
else
83+
num_macs=$(echo ${target} | cut -d - -f 3)
84+
fi
7585

7686
echo "--------------------------------------------------------------------------------"
7787
echo "Running ${elf_file} for ${target} run with FVP:${fvp_model} num_macs:${num_macs} timeout:${timeout}"
@@ -97,7 +107,44 @@ if [[ -n "${trace_file}" ]]; then
97107
extra_args_u85+=(-C "mps4_board.subsystem.ethosu.extra_args=--pmu-trace ${trace_file}")
98108
fi
99109

100-
if [[ ${target} == *"ethos-u55"* ]]; then
110+
if [[ ${target} == cortex-m* ]]; then
111+
[[ -z "${bundle_file}" ]] \
112+
&& { echo "[${BASH_SOURCE[0]}] --bundle=<BPTE_FILE> is required for cortex-m targets"; exit 1; }
113+
bundle_file=$(realpath "${bundle_file}")
114+
bundle_dir=$(dirname "${bundle_file}")
115+
bundle_name=$(basename "${bundle_file}")
116+
# Bundled-IO runner needs -i to point at a real file even though
117+
# inputs come from the bundle.
118+
dd if=/dev/zero of="${bundle_dir}/fvp_dummy_input.bin" bs=4 count=1 2>/dev/null
119+
${nobuf} ${fvp_model} \
120+
-C ethosu.num_macs=${num_macs} \
121+
-C mps3_board.visualisation.disable-visualisation=1 \
122+
-C mps3_board.telnetterminal0.start_telnet=0 \
123+
-C mps3_board.uart0.out_file='-' \
124+
-C mps3_board.uart0.shutdown_on_eot=1 \
125+
-C cpu0.semihosting-enable=1 \
126+
-C cpu0.semihosting-stack_base=0 \
127+
-C cpu0.semihosting-heap_limit=0 \
128+
-C "cpu0.semihosting-cwd=${bundle_dir}" \
129+
-C "ethosu.extra_args=--fast" \
130+
-C "cpu0.semihosting-cmd_line=executor_runner -m ${bundle_name} -i fvp_dummy_input.bin -o out" \
131+
-a "${elf_file}" \
132+
--timelimit ${timeout} 2>&1 | sed 's/\r$//' | tee ${log_file} || true
133+
echo "[${BASH_SOURCE[0]}] Simulation complete, $?"
134+
if grep -q "Test_result: PASS" "${log_file}"; then
135+
echo "[${BASH_SOURCE[0]}] Bundled I/O check PASSED for ${bundle_name}"
136+
rm "${log_file}"
137+
exit 0
138+
elif grep -q "Test_result: FAIL" "${log_file}"; then
139+
echo "[${BASH_SOURCE[0]}] Bundled I/O check FAILED for ${bundle_name}"
140+
rm "${log_file}"
141+
exit 1
142+
else
143+
echo "[${BASH_SOURCE[0]}] No Test_result line found in FVP output for ${bundle_name}"
144+
rm "${log_file}"
145+
exit 1
146+
fi
147+
elif [[ ${target} == *"ethos-u55"* ]]; then
101148
${nobuf} ${fvp_model} \
102149
-C ethosu.num_macs=${num_macs} \
103150
-C mps3_board.visualisation.disable-visualisation=1 \

backends/cortex_m/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ backends/cortex_m/test/build_test_runner.sh # Buil
2020
pytest --config-file=backends/arm/test/pytest.ini backends/cortex_m/test # Run tests with correct configuration file
2121
```
2222

23+
For an end-to-end bundled-IO FVP run of a single model (export → build → FVP → `Test_result: PASS`), use `examples/arm/run.sh`:
24+
```
25+
examples/arm/run.sh --model_name=<model> --target=cortex-m55+int8 --bundleio
26+
```
27+
This drives `aot_arm_compiler --bundleio`, invokes `build_test_runner.sh`, and launches the Corstone-300 FVP via `backends/arm/scripts/run_fvp.sh`.
28+
2329
## Supported operators
2430
Refer to `backends/cortex_m/test/ops` for currently supported accelerated ops/dtypes. Additionally, the quantizer targets pure "data-movement ops" such as data copies, slicing and concatenations to use quantized dtypes using the portable-kernels operator library.
2531
In general however, operators not supported by Cortex-M are kept in `fp32` using non-accelerated portable-kernels. It is recommended to analyze the graph after lowering to understand how much of the graph has been accelerated.

examples/arm/run.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ if [ "$perf_overlay" = true ] && [ "$model_explorer" != true ]; then
126126
exit 1
127127
fi
128128

129+
# Cortex-M backend is an operator-library, not a delegate; force-disable
130+
# --delegate when targeting cortex-m so users don't need --no_delegate.
131+
if [[ ${target} == cortex-m* ]]; then
132+
aot_arm_compiler_flag_delegate=""
133+
fi
134+
129135
if ! [[ ${pte_placement} == "elf" ]]; then
130136
if ! [[ "$pte_placement" =~ ^0x[0-9a-fA-F]{1,16}$ ]]; then
131137
echo "ERROR: Placing the PTE in memory failed, address is larger then 64bit $pte_placement"
@@ -217,6 +223,10 @@ function check_setup () {
217223
|| { echo "Executorch repo doesn't contain CMakeLists.txt file at root level"; return 1; }
218224

219225
backends/arm/scripts/build_executorch.sh --et_build_root="${et_build_root}" --build_type=$build_type $devtools_flag $et_dump_flag --toolchain="${toolchain}"
226+
elif [[ ${target} == cortex-m* ]]; then
227+
# build_test_runner.sh handles toolchain setup; just validate it's on PATH.
228+
hash arm-none-eabi-gcc \
229+
|| { echo "Could not find arm-none-eabi-gcc on PATH, ${_setup_msg}"; return 1; }
220230
elif [[ ${target} =~ "vgf" ]]; then
221231
model_converter=$(which model-converter)
222232
echo "${model_converter}"
@@ -347,6 +357,21 @@ for i in "${!test_model[@]}"; do
347357

348358
if [[ ${target} == *"TOSA"* ]]; then
349359
echo "Build for ${target} skip generating a .elf and running it"
360+
elif [[ ${target} == cortex-m* ]]; then
361+
# Cortex-M backend uses a shared semihosting executor_runner (built
362+
# by build_test_runner.sh) that loads the .bpte at runtime, rather
363+
# than per-model runners with the PTE baked in.
364+
if [ "$bundleio" != true ]; then
365+
echo "Error: --target=${target} requires --bundleio (the cortex-m runner loads bundled inputs via semihosting)"
366+
exit 1
367+
fi
368+
set -x
369+
backends/cortex_m/test/build_test_runner.sh
370+
cortex_m_elf="${et_root_dir}/arm_test/arm_semihosting_executor_runner_corstone-300/arm_executor_runner"
371+
if [ "$build_only" = false ] ; then
372+
backends/arm/scripts/run_fvp.sh --elf="${cortex_m_elf}" --target="${target}" --bundle="${pte_file}"
373+
fi
374+
set +x
350375
elif [[ ${target} == *"vgf"* ]]; then
351376
echo "Build and run for VKML, (target: ${target})"
352377
set -x

0 commit comments

Comments
 (0)