Skip to content

Commit 0f8ce1d

Browse files
tbitcsoz-agent
andcommitted
feat: MISRA-C CI, stack usage analysis, hw benchmark configs
REQ-BUILD-003: Add cppcheck MISRA C 2012 analysis CI job with fallback to built-in checks when the MISRA addon is unavailable. REQ-ARCH-040: Add hardware benchmark configuration for cycle-accurate PID benchmarking on nucleo_f446re and nucleo_h743zi using DWT CYCCNT. - Add misra job to CI workflow with suppression for known deviations - Add -fstack-usage to twister unit tests with 512-byte limit check - Upload .su files as CI artifacts alongside twister results - Create docs/safety/MISRA_DEVIATIONS.md documenting Rules 1.1, 11.3, 14.3, 20.9, 21.6 - Create tests/benchmarks/hw_benchmark/ with DWT CYCCNT timing, board overlays, testcase.yaml, and documentation Co-Authored-By: Oz <oz-agent@warp.dev>
1 parent ad61bed commit 0f8ce1d

10 files changed

Lines changed: 534 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,41 @@ jobs:
9090
done
9191
exit $missing
9292
93+
misra:
94+
name: MISRA-C Analysis
95+
runs-on: ubuntu-latest
96+
steps:
97+
- uses: actions/checkout@v4
98+
99+
- name: Install cppcheck
100+
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends cppcheck
101+
102+
- name: Run cppcheck with MISRA C 2012 rules
103+
run: |
104+
# cppcheck's MISRA addon may not be available on ubuntu-latest;
105+
# fall back to built-in checks if the addon fails.
106+
if cppcheck --addon=misra --version 2>/dev/null; then
107+
cppcheck --addon=misra \
108+
--suppress=misra-c2012-1.1 \
109+
--suppress=misra-c2012-20.9 \
110+
--suppress=misra-c2012-21.6 \
111+
-I include/ \
112+
-isystem tools/clang-tidy-stubs \
113+
--error-exitcode=1 \
114+
--inline-suppr \
115+
lib/*.c
116+
else
117+
echo "MISRA addon not available — falling back to built-in checks"
118+
cppcheck \
119+
--enable=all \
120+
--suppress=missingIncludeSystem \
121+
-I include/ \
122+
-isystem tools/clang-tidy-stubs \
123+
--error-exitcode=1 \
124+
--inline-suppr \
125+
lib/*.c
126+
fi
127+
93128
zephyr:
94129
name: Zephyr Twister Tests
95130
runs-on: ubuntu-latest
@@ -170,13 +205,33 @@ jobs:
170205
~/zephyr-sdk/setup.sh -c
171206
172207
# Unit tests run as native executables on native_sim.
208+
# -fstack-usage emits .su files for stack analysis.
173209
- name: Twister — unit tests
174210
run: |
175211
west twister \
176212
-T app/tests/unit \
177213
-p native_sim \
178214
--inline-logs -v \
179-
-O twister-out/unit
215+
-O twister-out/unit \
216+
-- -DEXTRA_CFLAGS=-fstack-usage
217+
218+
- name: Check stack usage (max 512 bytes per function)
219+
run: |
220+
echo "--- Stack usage analysis ---"
221+
err=0
222+
while IFS= read -r sufile; do
223+
while IFS=$'\t' read -r func bytes type; do
224+
if [ "${bytes:-0}" -gt 512 ] 2>/dev/null; then
225+
echo "STACK EXCEEDED: $sufile: $func $bytes $type"
226+
err=1
227+
fi
228+
done < "$sufile"
229+
done < <(find twister-out/unit -name '*.su' 2>/dev/null)
230+
if [ $err -ne 0 ]; then
231+
echo "::error::One or more functions exceed 512-byte stack limit"
232+
exit 1
233+
fi
234+
echo "All functions within 512-byte stack limit"
180235
181236
# Benchmarks execute on native_sim; Twister captures timing output
182237
# as an artifact. pass/fail is gated on the final log line regex.
@@ -203,3 +258,11 @@ jobs:
203258
with:
204259
name: twister-results
205260
path: twister-out/
261+
262+
- name: Upload stack usage reports
263+
if: always()
264+
uses: actions/upload-artifact@v4
265+
with:
266+
name: stack-usage-reports
267+
path: twister-out/**/*.su
268+
if-no-files-found: ignore

docs/safety/MISRA_DEVIATIONS.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# MISRA C 2012 — Known Deviations
2+
3+
This document records intentional deviations from MISRA C:2012 rules in
4+
the arbiter engine source code (`lib/` and `include/`). Each deviation
5+
is tracked here with a rationale and scope so that safety audits can
6+
reference this file as the single source of truth.
7+
8+
## Deviation Table
9+
10+
| Rule | Category | Rationale | Location |
11+
|------|----------|-----------|----------|
12+
| 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 |
13+
| 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>` |
14+
| 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` |
15+
| 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()` |
16+
| 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` |
17+
18+
## Process
19+
20+
- New deviations must be added to this table **before** the corresponding
21+
code is merged.
22+
- Each entry must include the MISRA rule number, its category
23+
(Required / Advisory / Mandatory), a rationale, and the affected
24+
file(s).
25+
- The CI pipeline runs `cppcheck --addon=misra` with `--suppress` flags
26+
matching the rules listed above. Adding a new suppression in CI
27+
without a corresponding entry here is a policy violation.
28+
- This file is referenced by `docs/COMPLIANCE.md` and the safety case
29+
documentation in `safety/`.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# SPDX-License-Identifier: MIT
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(hw_benchmark)
6+
7+
# Reuse the PID benchmark's compiled model tables.
8+
target_sources(app PRIVATE
9+
src/main.c
10+
${CMAKE_CURRENT_LIST_DIR}/../pid_benchmark/src/arbiter_model.c
11+
)
12+
13+
target_include_directories(app PRIVATE
14+
${CMAKE_CURRENT_LIST_DIR}/../pid_benchmark/src
15+
)
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Hardware PID Benchmark
2+
3+
Cycle-accurate PID benchmark targeting real Cortex-M hardware.
4+
Uses the ARM DWT CYCCNT register for cycle-exact measurement on
5+
Cortex-M targets, and falls back to `clock_gettime` on `native_sim`.
6+
7+
## Supported Boards
8+
9+
- **nucleo_f446re** — STM32F446RE (Cortex-M4, 180 MHz)
10+
- **nucleo_h743zi** — STM32H743ZI (Cortex-M7, 480 MHz)
11+
- **native_sim** — Host execution (nanosecond timing)
12+
13+
## Building
14+
15+
```sh
16+
# For real hardware
17+
west build -b nucleo_f446re tests/benchmarks/hw_benchmark
18+
west build -b nucleo_h743zi tests/benchmarks/hw_benchmark
19+
20+
# For simulation
21+
west build -b native_sim tests/benchmarks/hw_benchmark
22+
```
23+
24+
## Flashing
25+
26+
Connect the Nucleo board via USB (ST-Link) and run:
27+
28+
```sh
29+
west flash
30+
```
31+
32+
Ensure your udev rules are configured for ST-Link. On Linux:
33+
34+
```sh
35+
# /etc/udev/rules.d/99-stlink.rules
36+
SUBSYSTEM=="usb", ATTR{idVendor}=="0483", ATTR{idProduct}=="374b", MODE="0666"
37+
```
38+
39+
## Capturing Results
40+
41+
Open a serial terminal at 115200 baud on the board's virtual COM port:
42+
43+
```sh
44+
# Linux — typical device path for Nucleo boards
45+
picocom -b 115200 /dev/ttyACM0
46+
47+
# Or use west
48+
west espressif monitor # (for ESP boards)
49+
# For STM32 Nucleo, use any serial terminal on the ST-Link VCP
50+
```
51+
52+
Press the board's reset button to re-run the benchmark.
53+
54+
### Expected Output
55+
56+
```
57+
[00:00:00.000,000] <inf> hw_bench: === HW PID Benchmark ===
58+
[00:00:00.000,000] <inf> hw_bench: Timer unit: cycles
59+
[00:00:00.000,000] <inf> hw_bench: Iterations: 10000 (warmup: 500)
60+
[00:00:00.xxx,xxx] <inf> hw_bench: --- Hand-coded PID ---
61+
[00:00:00.xxx,xxx] <inf> hw_bench: Total: NNNN cycles (NN cycles/tick)
62+
[00:00:00.xxx,xxx] <inf> hw_bench: RAM (struct): 24 bytes
63+
[00:00:00.xxx,xxx] <inf> hw_bench: --- arbiter Engine PID ---
64+
[00:00:00.xxx,xxx] <inf> hw_bench: Total: NNNN cycles (NN cycles/tick)
65+
[00:00:00.xxx,xxx] <inf> hw_bench: RAM (ctx): NNN bytes
66+
[00:00:00.xxx,xxx] <inf> hw_bench: === Comparison ===
67+
[00:00:00.xxx,xxx] <inf> hw_bench: Engine overhead: NN% (NN vs NN cycles/tick)
68+
[00:00:00.xxx,xxx] <inf> hw_bench: HW Benchmark complete
69+
```
70+
71+
## Running via Twister
72+
73+
```sh
74+
# Build-only test (no hardware required)
75+
west twister -T tests/benchmarks/hw_benchmark -p native_sim
76+
77+
# With real hardware (requires connected board and runner configured)
78+
west twister -T tests/benchmarks/hw_benchmark -p nucleo_f446re --device-testing
79+
```
80+
81+
## How It Works
82+
83+
### DWT CYCCNT (Cortex-M)
84+
85+
The Data Watchpoint and Trace (DWT) unit provides a 32-bit free-running
86+
cycle counter (`CYCCNT`). At typical Cortex-M clock speeds:
87+
88+
- **180 MHz (F446RE)**: wraps every ~23.8 seconds
89+
- **480 MHz (H743ZI)**: wraps every ~8.9 seconds
90+
91+
The benchmark completes well within these limits.
92+
93+
### Measurement Methodology
94+
95+
1. **Warmup**: 500 iterations to stabilize caches and branch predictors.
96+
2. **Measured window**: 10,000 iterations of the PID loop.
97+
3. **Both implementations** (hand-coded and arbiter engine) use identical
98+
inputs and produce identical control outputs.
99+
4. The cycle count delta is divided by the iteration count for per-tick cost.
100+
101+
### ROM Comparison
102+
103+
ROM cannot be measured at runtime. After building, compare `.elf` sizes:
104+
105+
```sh
106+
arm-none-eabi-size build/zephyr/zephyr.elf
107+
```
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/* SPDX-License-Identifier: MIT */
2+
/* Minimal overlay for STM32F446RE — no board-specific DTS changes needed. */
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/* SPDX-License-Identifier: MIT */
2+
/* Minimal overlay for STM32H743ZI — no board-specific DTS changes needed. */
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
CONFIG_ARBITER=y
2+
CONFIG_ARBITER_HW_ACCEL=y
3+
CONFIG_TIMING_FUNCTIONS=y
4+
CONFIG_LOG=y
5+
CONFIG_PRINTK=y
6+
CONFIG_MAIN_STACK_SIZE=4096
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
sample:
2+
name: Hardware PID Benchmark
3+
description: |
4+
Cycle-accurate PID benchmark targeting real Cortex-M hardware.
5+
Uses DWT CYCCNT on Cortex-M targets and Zephyr timing API
6+
on native_sim for comparative measurement.
7+
platforms:
8+
- nucleo_f446re
9+
- nucleo_h743zi
10+
- native_sim

0 commit comments

Comments
 (0)