Skip to content

Commit 2c72415

Browse files
hanno-beckermkannwischer
authored andcommitted
Zephyr: Reorganize build around CUSTOM_BUILD facility
Previously, the Zephyr tests requires special rules per test. This commit instead rewrites the Zephyr test platform to merely register a CUSTOM_BUILD macro replacing the default compilation+linking step with a native CMake build of a Zephyr test application wrapping the test sources. All standard CFLAGS are forwarded, modulo some renaming. Signed-off-by: Hanno Becker <beckphan@amazon.co.uk>
1 parent 257ebcc commit 2c72415

5 files changed

Lines changed: 145 additions & 78 deletions

File tree

.github/workflows/zephyr.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ jobs:
4242
alloc: false
4343
rng_fail: false
4444
check_namespace: false
45+
# Zephyr's CMake selects the target arch; disable the host-arch
46+
# auto-detection that would otherwise leak into the forwarded CFLAGS.
47+
extra_args: --no-auto
4548
# Smoke only: QEMU doesn't model cycle counts (real numbers come from the
4649
# FPGA); this just exercises the bench build + run.
4750
- name: bench (smoke)
@@ -50,5 +53,5 @@ jobs:
5053
ZEPHYR_TARGET: ${{ matrix.target.board }}
5154
run: |
5255
opt=${{ matrix.target.opt == 'all' && 'opt' || 'no_opt' }}
53-
nix develop .#zephyr --command ./scripts/tests bench -c PMU --opt=$opt
54-
nix develop .#zephyr --command ./scripts/tests bench --components -c PMU --opt=$opt
56+
nix develop .#zephyr --command ./scripts/tests bench --no-auto -c PMU --opt=$opt
57+
nix develop .#zephyr --command ./scripts/tests bench --no-auto --components -c PMU --opt=$opt

test/zephyr/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[//]: # (SPDX-License-Identifier: CC-BY-4.0)
2+
3+
# Zephyr test platform
4+
5+
This is a test platform that builds the mlkem-native test applications as
6+
[Zephyr](https://www.zephyrproject.org/) applications, so they can run on
7+
QEMU-emulated Arm MPS boards. It covers Cortex-M3/M4/M7/M33/M55 through a
8+
single platform, without the need for per-board hardware abstraction layers.
9+
10+
## Usage
11+
12+
The platform is selected through the `EXTRA_MAKEFILE` environment variable and
13+
driven by the usual test targets from the top-level `Makefile`. It must run
14+
inside the `zephyr` Nix development shell, which provides the Arm toolchain,
15+
QEMU, and the Zephyr tree (`ZEPHYR_BASE`). The board is chosen with the
16+
`ZEPHYR_TARGET` environment variable (default `mps3-an547`):
17+
18+
```
19+
export EXTRA_MAKEFILE=test/zephyr/platform.mk
20+
export ZEPHYR_TARGET=mps3-an547
21+
nix develop .#zephyr --command ./scripts/tests func --opt=opt
22+
```
23+
24+
Currently supported targets:
25+
26+
| `ZEPHYR_TARGET` | Zephyr board | QEMU machine | Core |
27+
| --------------- | --------------------- | ------------ | ---------- |
28+
| `mps2-an385` | `mps2/an385` | `mps2-an385` | Cortex-M3 |
29+
| `mps2-an386` | `mps2/an386` | `mps2-an386` | Cortex-M4 |
30+
| `mps2-an500` | `mps2/an500` | `mps2-an500` | Cortex-M7 |
31+
| `mps2-an521` | `mps2/an521/cpu0` | `mps2-an521` | Cortex-M33 |
32+
| `mps3-an547` | `mps3/corstone300/an547` | `mps3-an547` | Cortex-M55 |
33+
34+
The Armv8.1-M MVE FIPS202 backend is an `OPT=1` feature and is built for
35+
`mps3-an547` only (the only listed core with MVE).
36+
37+
## How it works
38+
39+
Test binaries are built as Zephyr applications using Zephyr's standard
40+
CMake-based build, registered as a `CUSTOM_BUILD` hook in mlkem-native's
41+
makefiles.
42+
43+
The Zephyr application lives in `app/`:
44+
45+
- `app/CMakeLists.txt` compiles the `mlkem_native.c` amalgamation, the test
46+
sources, and `shim.c`. The test's `main()` is renamed to `mlk_test_main` via
47+
`-Dmain=mlk_test_main` so the shim can own `main()`.
48+
- `app/shim.c` fetches argv via semihosting, calls `mlk_test_main`, and exits
49+
QEMU with the test's return code (again via semihosting).
50+
- `app/Kconfig` force-selects the FPU/MVE features for Corstone-300, whose
51+
Zephyr SoC does not otherwise advertise MVE.
52+
53+
`platform.mk` forwards the test binary's full `CFLAGS` to the CMake build, so
54+
the firmware is compiled with the project's own warning/optimization/standard
55+
policy and feature defines rather than a divergent Zephyr default.
56+
57+
`exec_wrapper.py` runs a built ELF under QEMU (`-nographic`, semihosting on),
58+
forwarding the guest's exit code; it is wired in as `EXEC_WRAPPER`.

test/zephyr/app/CMakeLists.txt

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,45 @@ project(mlkem_native_zephyr)
77

88
# Parameters supplied via -D from platform.mk:
99
# ZEPHYR_NATIVE_ROOT - mlkem-native checkout the test is built from
10-
# ZEPHYR_LEVEL - ML-KEM parameter set: 512 | 768 | 1024
11-
# ZEPHYR_TEST_SRC - test entrypoint, relative to ZEPHYR_NATIVE_ROOT
12-
# ZEPHYR_TEST_DEFS - optional extra -D defines (space separated)
10+
# ZEPHYR_TEST_SRCS - test sources (entrypoint + support, e.g. notrandombytes
11+
# and -- for bench -- the HAL), relative to
12+
# ZEPHYR_NATIVE_ROOT, space separated
13+
# ZEPHYR_TEST_CFLAGS - the test binary's CFLAGS (see below); includes the
14+
# parameter set (-DMLK_CONFIG_PARAMETER_SET=...)
1315
set(R ${ZEPHYR_NATIVE_ROOT})
1416

17+
separate_arguments(_test_srcs UNIX_COMMAND "${ZEPHYR_TEST_SRCS}")
18+
list(TRANSFORM _test_srcs PREPEND ${R}/)
19+
1520
target_sources(app PRIVATE
1621
${R}/mlkem/mlkem_native.c
17-
${R}/${ZEPHYR_TEST_SRC}
18-
${R}/test/notrandombytes/notrandombytes.c
22+
${_test_srcs}
1923
${CMAKE_CURRENT_SOURCE_DIR}/shim.c
2024
)
2125

2226
target_include_directories(app PRIVATE
2327
${R}/mlkem
2428
${R}/test/notrandombytes
29+
${R}/test/hal
2530
)
2631

27-
target_compile_definitions(app PRIVATE MLK_CONFIG_PARAMETER_SET=${ZEPHYR_LEVEL})
28-
29-
if(ZEPHYR_TEST_DEFS)
30-
separate_arguments(_defs UNIX_COMMAND "${ZEPHYR_TEST_DEFS}")
31-
target_compile_definitions(app PRIVATE ${_defs})
32+
# The test binary's CFLAGS, forwarded from the build (test/zephyr/platform.mk
33+
# assembles and escapes them) and applied to the mlkem amalgamation and the test
34+
# sources alike, so both honour the project's own warning/opt/std policy and
35+
# per-test feature defines rather than a divergent Zephyr default.
36+
if(ZEPHYR_TEST_CFLAGS)
37+
separate_arguments(_cflags UNIX_COMMAND "${ZEPHYR_TEST_CFLAGS}")
38+
target_compile_options(app PRIVATE ${_cflags})
3239
endif()
3340

41+
# The benchmark HAL is the only test source that pulls in Zephyr SDK headers
42+
# (<zephyr/kernel.h> for the cycle counter). Those headers are not clean under
43+
# the project's strict, -Werror'd warning set forwarded above (e.g.
44+
# -Wconversion, -Wsign-conversion, -pedantic), so drop -Werror just for this
45+
# file -- our own sources stay strict.
46+
set_source_files_properties(${R}/test/hal/hal.c
47+
PROPERTIES COMPILE_OPTIONS "-Wno-error")
48+
3449
# Optional native FIPS202 backend (e.g. Armv8.1-M MVE on Cortex-M55). The
3550
# monolithic mlkem_native.c already includes the backend C sources; its
3651
# assembly counterpart comes from mlkem_native_asm.S.
@@ -41,13 +56,9 @@ if(ZEPHYR_FIPS202_BACKEND)
4156
"MLK_CONFIG_FIPS202_BACKEND_FILE=\"${ZEPHYR_FIPS202_BACKEND}\"")
4257
endif()
4358

44-
# The bench tests need the cycle-counting HAL.
45-
if(ZEPHYR_TEST_HAL)
46-
target_sources(app PRIVATE ${R}/test/hal/hal.c)
47-
target_include_directories(app PRIVATE ${R}/test/hal)
48-
endif()
49-
5059
# Each test brings its own int main(void); rename it so the Zephyr shim
51-
# (shim.c) owns main() and can stop QEMU with the test's exit code.
52-
set_source_files_properties(${R}/${ZEPHYR_TEST_SRC}
60+
# (shim.c) owns main() and can stop QEMU with the test's exit code. Only the
61+
# test entrypoint in ZEPHYR_TEST_SRCS defines main(); the support sources
62+
# (notrandombytes, hal) don't, so applying the define to all is harmless.
63+
set_source_files_properties(${_test_srcs}
5364
PROPERTIES COMPILE_DEFINITIONS "main=mlk_test_main")

test/zephyr/app/Kconfig

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
# QEMU's mps3-an547) has it. Force-select it to build the Armv8.1-M MVE FIPS202
66
# backend. Enabled per target by platform.mk.
77
config FIPS202_MVE_BACKEND
8-
bool "Build with the Armv8.1-M MVE FIPS202 backend"
9-
select FPU
10-
select ARMV8_M_DSP
11-
select ARMV8_1_M_MVEI
12-
select ARMV8_1_M_MVEF
8+
bool "Build with the Armv8.1-M MVE FIPS202 backend"
9+
select FPU
10+
select ARMV8_M_DSP
11+
select ARMV8_1_M_MVEI
12+
select ARMV8_1_M_MVEF
1313

1414
source "Kconfig.zephyr"

test/zephyr/platform.mk

Lines changed: 48 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,34 @@
11
# Copyright (c) The mlkem-native project authors
22
# SPDX-License-Identifier: Apache-2.0 OR ISC OR MIT
33

4+
# Zephyr test platform for QEMU-emulated Arm MPS boards.
5+
#
6+
# Each test binary is built as a Zephyr application by CMake (which owns the arm
7+
# toolchain, per-board arch flags and libc via the .#zephyr dev shell). The
8+
# generic rules don't link these; CUSTOM_BUILD (below, see test/mk/rules.mk)
9+
# drives the CMake build from the binary's TEST_SRCS, set by components.mk --
10+
# adding a test binary there needs no change here.
11+
412
PLATFORM_PATH := test/zephyr
513

614
# BUILD_DIR is set by the top-level Makefile after this file is included;
7-
# define it here too so the explicit bin rules below expand to the right path.
15+
# define it here too so CUSTOM_BUILD below expands to the right path.
816
BUILD_DIR ?= test/build
917

1018
# ZEPHYR_TARGET=<key> selects a target. Each key maps to a Zephyr board and the
1119
# QEMU machine emulating it; add a board with a row below.
1220
ZEPHYR_TARGET ?= mps3-an547
1321

1422
ZEPHYR_BOARD_mps2-an385 := mps2/an385
15-
ZEPHYR_QEMU_mps2-an385 := mps2-an385 # Cortex-M3
23+
ZEPHYR_QEMU_mps2-an385 := mps2-an385 # Cortex-M3
1624
ZEPHYR_BOARD_mps2-an386 := mps2/an386
17-
ZEPHYR_QEMU_mps2-an386 := mps2-an386 # Cortex-M4
25+
ZEPHYR_QEMU_mps2-an386 := mps2-an386 # Cortex-M4
1826
ZEPHYR_BOARD_mps2-an500 := mps2/an500
19-
ZEPHYR_QEMU_mps2-an500 := mps2-an500 # Cortex-M7
27+
ZEPHYR_QEMU_mps2-an500 := mps2-an500 # Cortex-M7
2028
ZEPHYR_BOARD_mps2-an521 := mps2/an521/cpu0
21-
ZEPHYR_QEMU_mps2-an521 := mps2-an521 # Cortex-M33
29+
ZEPHYR_QEMU_mps2-an521 := mps2-an521 # Cortex-M33
2230
ZEPHYR_BOARD_mps3-an547 := mps3/corstone300/an547
23-
ZEPHYR_QEMU_mps3-an547 := mps3-an547 # Cortex-M55
31+
ZEPHYR_QEMU_mps3-an547 := mps3-an547 # Cortex-M55
2432

2533
ZEPHYR_FIPS202_BACKEND_mps3-an547 := fips202/native/armv81m/mve.h
2634

@@ -33,11 +41,8 @@ ifeq ($(ZEPHYR_BOARD),)
3341
$(error Unknown ZEPHYR_TARGET '$(ZEPHYR_TARGET)'. Supported: $(ZEPHYR_TARGETS))
3442
endif
3543

36-
# The test binaries are built by Zephyr's CMake (which uses its own arm
37-
# toolchain via the .#zephyr dev shell), not the generic Make rules. The
38-
# top-level targets still attach the usual object/library prerequisites to the
39-
# bin paths; with OPT=0 those are portable host objects that compile cleanly
40-
# and are simply discarded (the Zephyr ELF is copied over them).
44+
# CUSTOM_BUILD must be set before components.mk is included so it switches that
45+
# file to source prerequisites (it is: the top-level Makefile includes us first).
4146
OPT ?= 0
4247

4348
# Native backends are an OPT=1 feature (an547 builds the Armv8.1-M MVE backend).
@@ -46,51 +51,41 @@ ZEPHYR_FIPS202_BACKEND := $(if $(filter 1,$(OPT)),$(strip $(ZEPHYR_FIPS202_BACKE
4651
ZEPHYR_APP := $(PLATFORM_PATH)/app
4752
ZEPHYR_BUILD_DIR := $(BUILD_DIR)/zephyr/$(ZEPHYR_TARGET)
4853

49-
# Build a test as a Zephyr application and drop the resulting ELF at the path
50-
# the top-level Makefile expects. An explicit rule for the exact bin path wins
51-
# over the generic link pattern rule in test/mk/rules.mk.
52-
# $(1) level $(2) bin name $(3) test source (repo-relative) $(4) extra -D
53-
define ZEPHYR_BIN
54-
$(BUILD_DIR)/mlkem$(1)/bin/$(2):
55-
$$(Q)echo " ZEPHYR $(ZEPHYR_TARGET) ML-KEM-$(1): $(3)"
56-
$$(Q)cmake -GNinja -S $(ZEPHYR_APP) -B $(ZEPHYR_BUILD_DIR)/$(2) \
54+
# Per-binary CMake build dir, keyed on $(notdir $@) so binaries build in
55+
# parallel. Recipe-expanded, so $@ is the specific bin being built.
56+
ZEPHYR_OUT = $(ZEPHYR_BUILD_DIR)/$(notdir $@)
57+
58+
# Shrink the test iteration counts (the sources default them higher, sized for
59+
# native hardware): QEMU is far slower. On CFLAGS so they forward below.
60+
CFLAGS += -DNTESTS_FUNC=3 -DNTESTS_KAT=100 \
61+
-DMLK_BENCHMARK_NTESTS=10 -DMLK_BENCHMARK_NITERATIONS=10 -DMLK_BENCHMARK_NWARMUP=10 \
62+
-DNUM_RANDOM_TESTS=100 -DNUM_RANDOM_TESTS_REJ_UNIFORM=100 -DMAX_INTT_CONSTANT_COEFF=512
63+
64+
# The binary's CFLAGS, forwarded to the CMake build (which applies them to the
65+
# mlkem amalgamation and test sources alike). '=' not ':=', so the recipe-time
66+
# $(CFLAGS) includes the binary's target-specific additions (e.g.
67+
# -DMLK_STATIC_TESTABLE= for test_unit, -DMLK_CONFIG_FILE="..." for test_alloc).
68+
# Two rewrites:
69+
# - -Imlkem -> absolute, as CMake builds from its own dir, not the repo root
70+
# (and the alloc config path is relative to -Imlkem);
71+
# - \" -> \\", so one level of quoting survives each of the recipe shell and
72+
# CMake's separate_arguments and reaches the compiler intact.
73+
# Requires AUTO=0 (see .github/workflows/zephyr.yml): the host-arch flags AUTO=1
74+
# adds must not reach the Zephyr toolchain, which selects the target arch itself.
75+
ZEPHYR_TEST_CFLAGS = $(subst \",\\\",$(patsubst -Imlkem,-I$(abspath mlkem),$(CFLAGS)))
76+
77+
CUSTOM_BUILD = \
78+
echo " ZEPHYR $(ZEPHYR_TARGET): $(notdir $@)" && \
79+
cmake -GNinja -S $(ZEPHYR_APP) -B $(ZEPHYR_OUT) \
5780
-DBOARD=$(ZEPHYR_BOARD) \
5881
-DZEPHYR_NATIVE_ROOT=$(CURDIR) \
59-
-DZEPHYR_LEVEL=$(1) \
60-
-DZEPHYR_TEST_SRC=$(3) \
61-
-DZEPHYR_TEST_DEFS="NTESTS_FUNC=3 NTESTS_KAT=100 MLK_BENCHMARK_NTESTS=10 MLK_BENCHMARK_NITERATIONS=10 MLK_BENCHMARK_NWARMUP=10" \
82+
-DZEPHYR_TEST_SRCS="$(strip $(TEST_SRCS))" \
83+
-DZEPHYR_TEST_CFLAGS="$(ZEPHYR_TEST_CFLAGS)" \
6284
-DZEPHYR_FIPS202_BACKEND=$(ZEPHYR_FIPS202_BACKEND) \
6385
$(if $(ZEPHYR_FIPS202_BACKEND),-DCONFIG_FIPS202_MVE_BACKEND=y) \
64-
$(4) \
65-
-DUSER_CACHE_DIR=$(abspath $(ZEPHYR_BUILD_DIR)/$(2)/.cache) \
66-
>/dev/null
67-
$$(Q)cmake --build $(ZEPHYR_BUILD_DIR)/$(2) >/dev/null
68-
$$(Q)[ -d $$(@D) ] || mkdir -p $$(@D)
69-
$$(Q)cp $(ZEPHYR_BUILD_DIR)/$(2)/zephyr/zephyr.elf $$@
70-
endef
71-
72-
$(eval $(call ZEPHYR_BIN,512,test_mlkem512,test/src/test_mlkem.c))
73-
$(eval $(call ZEPHYR_BIN,768,test_mlkem768,test/src/test_mlkem.c))
74-
$(eval $(call ZEPHYR_BIN,1024,test_mlkem1024,test/src/test_mlkem.c))
75-
76-
$(eval $(call ZEPHYR_BIN,512,gen_KAT512,test/src/gen_KAT.c))
77-
$(eval $(call ZEPHYR_BIN,768,gen_KAT768,test/src/gen_KAT.c))
78-
$(eval $(call ZEPHYR_BIN,1024,gen_KAT1024,test/src/gen_KAT.c))
79-
80-
$(eval $(call ZEPHYR_BIN,512,acvp_mlkem512,test/acvp/acvp_mlkem.c))
81-
$(eval $(call ZEPHYR_BIN,768,acvp_mlkem768,test/acvp/acvp_mlkem.c))
82-
$(eval $(call ZEPHYR_BIN,1024,acvp_mlkem1024,test/acvp/acvp_mlkem.c))
83-
84-
$(eval $(call ZEPHYR_BIN,512,wycheproof_mlkem512,test/wycheproof/wycheproof_mlkem.c))
85-
$(eval $(call ZEPHYR_BIN,768,wycheproof_mlkem768,test/wycheproof/wycheproof_mlkem.c))
86-
$(eval $(call ZEPHYR_BIN,1024,wycheproof_mlkem1024,test/wycheproof/wycheproof_mlkem.c))
87-
88-
$(eval $(call ZEPHYR_BIN,512,bench_mlkem512,test/bench/bench_mlkem.c,-DZEPHYR_TEST_HAL=ON))
89-
$(eval $(call ZEPHYR_BIN,768,bench_mlkem768,test/bench/bench_mlkem.c,-DZEPHYR_TEST_HAL=ON))
90-
$(eval $(call ZEPHYR_BIN,1024,bench_mlkem1024,test/bench/bench_mlkem.c,-DZEPHYR_TEST_HAL=ON))
91-
92-
$(eval $(call ZEPHYR_BIN,512,bench_components_mlkem512,test/bench/bench_components_mlkem.c,-DZEPHYR_TEST_HAL=ON))
93-
$(eval $(call ZEPHYR_BIN,768,bench_components_mlkem768,test/bench/bench_components_mlkem.c,-DZEPHYR_TEST_HAL=ON))
94-
$(eval $(call ZEPHYR_BIN,1024,bench_components_mlkem1024,test/bench/bench_components_mlkem.c,-DZEPHYR_TEST_HAL=ON))
86+
-DUSER_CACHE_DIR=$(abspath $(ZEPHYR_OUT)/.cache) \
87+
>/dev/null && \
88+
cmake --build $(ZEPHYR_OUT) >/dev/null && \
89+
cp $(ZEPHYR_OUT)/zephyr/zephyr.elf $@
9590

9691
EXEC_WRAPPER := $(abspath $(PLATFORM_PATH)/exec_wrapper.py)

0 commit comments

Comments
 (0)