Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,41 @@ jobs:
done
exit $missing

misra:
name: MISRA-C Analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install cppcheck
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cppcheck

- name: Run cppcheck with MISRA C 2012 rules
run: |
# cppcheck's MISRA addon may not be available on ubuntu-latest;
# fall back to built-in checks if the addon fails.
if cppcheck --addon=misra --version 2>/dev/null; then
cppcheck --addon=misra \
--suppress=misra-c2012-1.1 \
--suppress=misra-c2012-20.9 \
--suppress=misra-c2012-21.6 \
-I include/ \
-isystem tools/clang-tidy-stubs \
--error-exitcode=1 \
--inline-suppr \
lib/*.c
else
echo "MISRA addon not available — falling back to built-in checks"
cppcheck \
--enable=all \
--suppress=missingIncludeSystem \
-I include/ \
-isystem tools/clang-tidy-stubs \
--error-exitcode=1 \
--inline-suppr \
lib/*.c
fi

zephyr:
name: Zephyr Twister Tests
runs-on: ubuntu-latest
Expand Down Expand Up @@ -170,13 +205,33 @@ jobs:
~/zephyr-sdk/setup.sh -c

# Unit tests run as native executables on native_sim.
# -fstack-usage emits .su files for stack analysis.
- name: Twister — unit tests
run: |
west twister \
-T app/tests/unit \
-p native_sim \
--inline-logs -v \
-O twister-out/unit
-O twister-out/unit \
-- -DEXTRA_CFLAGS=-fstack-usage

- name: Check stack usage (max 512 bytes per function)
run: |
echo "--- Stack usage analysis ---"
err=0
while IFS= read -r sufile; do
while IFS=$'\t' read -r func bytes type; do
if [ "${bytes:-0}" -gt 512 ] 2>/dev/null; then
echo "STACK EXCEEDED: $sufile: $func $bytes $type"
err=1
fi
done < "$sufile"
done < <(find twister-out/unit -name '*.su' 2>/dev/null)
if [ $err -ne 0 ]; then
echo "::error::One or more functions exceed 512-byte stack limit"
exit 1
fi
echo "All functions within 512-byte stack limit"

# Benchmarks execute on native_sim; Twister captures timing output
# as an artifact. pass/fail is gated on the final log line regex.
Expand All @@ -203,3 +258,11 @@ jobs:
with:
name: twister-results
path: twister-out/

- name: Upload stack usage reports
if: always()
uses: actions/upload-artifact@v4
with:
name: stack-usage-reports
path: twister-out/**/*.su
if-no-files-found: ignore
29 changes: 29 additions & 0 deletions docs/safety/MISRA_DEVIATIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# MISRA C 2012 — Known Deviations

This document records intentional deviations from MISRA C:2012 rules in
the arbiter engine source code (`lib/` and `include/`). Each deviation
is tracked here with a rationale and scope so that safety audits can
reference this file as the single source of truth.

## Deviation Table

| Rule | Category | Rationale | Location |
|------|----------|-----------|----------|
| 1.1 — C11 required | Required | Zephyr RTOS requires C11 (e.g. `_Static_assert`, `_Atomic`, `<stdnoreturn.h>`). The arbiter engine targets Zephyr exclusively and cannot compile as C90/C99. | All translation units |
| 20.9 — `<stdio.h>` input/output | Required | Zephyr system headers (`<zephyr/kernel.h>`, `<zephyr/logging/log.h>`) are included via angle-bracket syntax and resolved by the Zephyr build system. These are not standard-library I/O headers. | All `#include <zephyr/*.h>` |
| 21.6 — Standard I/O functions | Required | The `LOG_INF`, `LOG_ERR`, `LOG_DBG` macros expand to `printf`-style formatting internally. The arbiter engine never calls `printf`/`fprintf` directly; all logging goes through Zephyr's `LOG_*` API, which may be compiled out via `CONFIG_LOG=n`. | `lib/arbiter_engine.c`, `lib/arbiter_eval.c`, `lib/arbiter_trace.c` |
| 11.3 — Cast between pointer to object and pointer to different object type | Required | The blob loader casts `const uint8_t *` to `const struct arbiter_model *` when deserialising a compiled model blob. The cast is guarded by alignment checks and a SHA-256 hash verification before any field access. | `lib/arbiter_blob.c` — `ARBITER_blob_load()` |
| 14.3 — Controlling expression is always true/false | Required | The `unlikely()` and `likely()` branch-hint macros (from `<zephyr/toolchain.h>`) wrap conditions in `__builtin_expect()`. Static analysers may report the inner expression as invariant. These are intentional performance annotations, not dead code. | `lib/arbiter_eval.c`, `lib/arbiter_fact_store.c` |

## Process

- New deviations must be added to this table **before** the corresponding
code is merged.
- Each entry must include the MISRA rule number, its category
(Required / Advisory / Mandatory), a rationale, and the affected
file(s).
- The CI pipeline runs `cppcheck --addon=misra` with `--suppress` flags
matching the rules listed above. Adding a new suppression in CI
without a corresponding entry here is a policy violation.
- This file is referenced by `docs/COMPLIANCE.md` and the safety case
documentation in `safety/`.
15 changes: 15 additions & 0 deletions tests/benchmarks/hw_benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: MIT

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(hw_benchmark)

# Reuse the PID benchmark's compiled model tables.
target_sources(app PRIVATE
src/main.c
${CMAKE_CURRENT_LIST_DIR}/../pid_benchmark/src/arbiter_model.c
)

target_include_directories(app PRIVATE
${CMAKE_CURRENT_LIST_DIR}/../pid_benchmark/src
)
107 changes: 107 additions & 0 deletions tests/benchmarks/hw_benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Hardware PID Benchmark

Cycle-accurate PID benchmark targeting real Cortex-M hardware.
Uses the ARM DWT CYCCNT register for cycle-exact measurement on
Cortex-M targets, and falls back to `clock_gettime` on `native_sim`.

## Supported Boards

- **nucleo_f446re** — STM32F446RE (Cortex-M4, 180 MHz)
- **nucleo_h743zi** — STM32H743ZI (Cortex-M7, 480 MHz)
- **native_sim** — Host execution (nanosecond timing)

## Building

```sh
# For real hardware
west build -b nucleo_f446re tests/benchmarks/hw_benchmark
west build -b nucleo_h743zi tests/benchmarks/hw_benchmark

# For simulation
west build -b native_sim tests/benchmarks/hw_benchmark
```

## Flashing

Connect the Nucleo board via USB (ST-Link) and run:

```sh
west flash
```

Ensure your udev rules are configured for ST-Link. On Linux:

```sh
# /etc/udev/rules.d/99-stlink.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="374b", MODE="0666"
```

## Capturing Results

Open a serial terminal at 115200 baud on the board's virtual COM port:

```sh
# Linux — typical device path for Nucleo boards
picocom -b 115200 /dev/ttyACM0

# Or use west
west espressif monitor # (for ESP boards)
# For STM32 Nucleo, use any serial terminal on the ST-Link VCP
```

Press the board's reset button to re-run the benchmark.

### Expected Output

```
[00:00:00.000,000] <inf> hw_bench: === HW PID Benchmark ===
[00:00:00.000,000] <inf> hw_bench: Timer unit: cycles
[00:00:00.000,000] <inf> hw_bench: Iterations: 10000 (warmup: 500)
[00:00:00.xxx,xxx] <inf> hw_bench: --- Hand-coded PID ---
[00:00:00.xxx,xxx] <inf> hw_bench: Total: NNNN cycles (NN cycles/tick)
[00:00:00.xxx,xxx] <inf> hw_bench: RAM (struct): 24 bytes
[00:00:00.xxx,xxx] <inf> hw_bench: --- arbiter Engine PID ---
[00:00:00.xxx,xxx] <inf> hw_bench: Total: NNNN cycles (NN cycles/tick)
[00:00:00.xxx,xxx] <inf> hw_bench: RAM (ctx): NNN bytes
[00:00:00.xxx,xxx] <inf> hw_bench: === Comparison ===
[00:00:00.xxx,xxx] <inf> hw_bench: Engine overhead: NN% (NN vs NN cycles/tick)
[00:00:00.xxx,xxx] <inf> hw_bench: HW Benchmark complete
```

## Running via Twister

```sh
# Build-only test (no hardware required)
west twister -T tests/benchmarks/hw_benchmark -p native_sim

# With real hardware (requires connected board and runner configured)
west twister -T tests/benchmarks/hw_benchmark -p nucleo_f446re --device-testing
```

## How It Works

### DWT CYCCNT (Cortex-M)

The Data Watchpoint and Trace (DWT) unit provides a 32-bit free-running
cycle counter (`CYCCNT`). At typical Cortex-M clock speeds:

- **180 MHz (F446RE)**: wraps every ~23.8 seconds
- **480 MHz (H743ZI)**: wraps every ~8.9 seconds

The benchmark completes well within these limits.

### Measurement Methodology

1. **Warmup**: 500 iterations to stabilize caches and branch predictors.
2. **Measured window**: 10,000 iterations of the PID loop.
3. **Both implementations** (hand-coded and arbiter engine) use identical
inputs and produce identical control outputs.
4. The cycle count delta is divided by the iteration count for per-tick cost.

### ROM Comparison

ROM cannot be measured at runtime. After building, compare `.elf` sizes:

```sh
arm-none-eabi-size build/zephyr/zephyr.elf
```
2 changes: 2 additions & 0 deletions tests/benchmarks/hw_benchmark/boards/nucleo_f446re.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* SPDX-License-Identifier: MIT */
/* Minimal overlay for STM32F446RE — no board-specific DTS changes needed. */
2 changes: 2 additions & 0 deletions tests/benchmarks/hw_benchmark/boards/nucleo_h743zi.overlay
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/* SPDX-License-Identifier: MIT */
/* Minimal overlay for STM32H743ZI — no board-specific DTS changes needed. */
6 changes: 6 additions & 0 deletions tests/benchmarks/hw_benchmark/prj.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CONFIG_ARBITER=y
CONFIG_ARBITER_HW_ACCEL=y
CONFIG_TIMING_FUNCTIONS=y
CONFIG_LOG=y
CONFIG_PRINTK=y
CONFIG_MAIN_STACK_SIZE=4096
10 changes: 10 additions & 0 deletions tests/benchmarks/hw_benchmark/sample.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sample:
name: Hardware PID Benchmark
description: |
Cycle-accurate PID benchmark targeting real Cortex-M hardware.
Uses DWT CYCCNT on Cortex-M targets and Zephyr timing API
on native_sim for comparative measurement.
platforms:
- nucleo_f446re
- nucleo_h743zi
- native_sim
Loading
Loading