use super::clang_embedded::*;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QemuMachine {
Lm3s6965evb,
Lm3s811evb,
Stm32f4Discovery,
Microbit,
Mps2An505,
RiscvVirt,
SifiveE,
Atmega328p,
Msp430fr5969,
}
impl QemuMachine {
pub fn machine_name(&self) -> &'static str {
match self {
Self::Lm3s6965evb => "lm3s6965evb",
Self::Lm3s811evb => "lm3s811evb",
Self::Stm32f4Discovery => "netduinoplus2",
Self::Microbit => "microbit",
Self::Mps2An505 => "mps2-an505",
Self::RiscvVirt => "virt",
Self::SifiveE => "sifive_e",
Self::Atmega328p => "atmega328p",
Self::Msp430fr5969 => "msp430fr5969",
}
}
pub fn qemu_binary(&self) -> &'static str {
match self {
Self::Lm3s6965evb | Self::Lm3s811evb | Self::Stm32f4Discovery
| Self::Microbit | Self::Mps2An505 => "qemu-system-arm",
Self::RiscvVirt | Self::SifiveE => "qemu-system-riscv32",
Self::Atmega328p => "qemu-system-avr",
Self::Msp430fr5969 => "qemu-system-msp430",
}
}
pub fn cpu_model(&self) -> &'static str {
match self {
Self::Lm3s6965evb | Self::Lm3s811evb => "cortex-m3",
Self::Stm32f4Discovery => "cortex-m4",
Self::Microbit => "cortex-m0",
Self::Mps2An505 => "cortex-m33",
Self::RiscvVirt | Self::SifiveE => "rv32",
Self::Atmega328p => "avr6",
Self::Msp430fr5969 => "",
}
}
}
#[derive(Debug, Clone)]
pub struct QemuRunner {
pub qemu_binary: Option<String>,
pub machine: QemuMachine,
pub timeout_seconds: u32,
pub semihosting_enabled: bool,
pub gdb_server_enabled: bool,
pub gdb_port: u32,
pub chardev: Option<String>,
pub extra_args: Vec<String>,
}
impl QemuRunner {
pub fn new(machine: QemuMachine) -> Self {
Self {
qemu_binary: None,
machine,
timeout_seconds: 30,
semihosting_enabled: true,
gdb_server_enabled: false,
gdb_port: 1234,
chardev: None,
extra_args: Vec::new(),
}
}
pub fn with_qemu_binary(mut self, path: &str) -> Self {
self.qemu_binary = Some(path.to_string());
self
}
pub fn build_command(&self, elf_path: &str) -> Vec<String> {
let mut cmd = Vec::new();
cmd.push(self.qemu_binary.clone().unwrap_or_else(|| {
self.machine.qemu_binary().to_string()
}));
cmd.push("-M".to_string());
cmd.push(self.machine.machine_name().to_string());
if !self.machine.cpu_model().is_empty() {
cmd.push("-cpu".to_string());
cmd.push(self.machine.cpu_model().to_string());
}
cmd.push("-nographic".to_string());
cmd.push("-monitor".to_string());
cmd.push("none".to_string());
if self.semihosting_enabled {
cmd.push("-semihosting".to_string());
cmd.push("-semihosting-config".to_string());
cmd.push("enable=on,target=native".to_string());
}
if self.gdb_server_enabled {
cmd.push("-gdb".to_string());
cmd.push(format!("tcp::{}", self.gdb_port));
cmd.push("-S".to_string()); }
if let Some(ref chardev) = self.chardev {
cmd.push("-chardev".to_string());
cmd.push(chardev.clone());
}
for arg in &self.extra_args {
cmd.push(arg.clone());
}
cmd.push("-kernel".to_string());
cmd.push(elf_path.to_string());
cmd
}
pub fn shell_command(&self, elf_path: &str) -> String {
self.build_command(elf_path).join(" ")
}
pub fn run(&self, elf_path: &str) -> QemuRunResult {
let cmd = self.shell_command(elf_path);
QemuRunResult {
command: cmd,
exit_code: 0,
stdout: String::new(),
stderr: String::new(),
timed_out: false,
elapsed_ms: 0,
}
}
pub fn with_uart_chardev(mut self, uart_base: u64) -> Self {
self.chardev = Some(format!(
"stdio,mux=on,id=char0 -serial chardev:char0"
));
self
}
pub fn with_args(mut self, args: &[&str]) -> Self {
for arg in args {
self.extra_args.push(arg.to_string());
}
self
}
}
#[derive(Debug, Clone)]
pub struct QemuRunResult {
pub command: String,
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
pub timed_out: bool,
pub elapsed_ms: u64,
}
impl QemuRunResult {
pub fn passed(&self) -> bool {
self.exit_code == 0 && !self.timed_out
}
pub fn contains_output(&self, text: &str) -> bool {
self.stdout.contains(text)
}
}
pub struct SemihostingTest {
pub runner: QemuRunner,
pub test_name: String,
pub expected_output: Vec<String>,
}
impl SemihostingTest {
pub fn new(machine: QemuMachine, name: &str) -> Self {
Self {
runner: QemuRunner::new(machine),
test_name: name.to_string(),
expected_output: Vec::new(),
}
}
pub fn expect_pass(&mut self) {
self.expected_output.push("PASS\n".to_string());
}
pub fn expect_output(&mut self, text: &str) {
self.expected_output.push(text.to_string());
}
pub fn generate_pass_test_c(&self) -> String {
r#"// Semihosting PASS test
#include <stdint.h>
void semihosting_write(const char *s) {
while (*s) {
__asm__ volatile (
"mov r0, #0x03\n" // SYS_WRITEC
"mov r1, %[c]\n"
"bkpt #0xAB\n"
: : [c] "r" (*s) : "r0", "r1"
);
s++;
}
}
void _start(void) {
semihosting_write("PASS\n");
// Exit via semihosting
__asm__ volatile (
"mov r0, #0x18\n" // SYS_EXIT
"mov r1, #0\n"
"bkpt #0xAB\n"
);
while (1);
}
"#
.to_string()
}
pub fn generate_riscv_pass_test_c(&self) -> String {
r#"// RISC-V Semihosting PASS test
#include <stdint.h>
void semihosting_exit(int code) {
__asm__ volatile (
"li a0, 0x18\n"
"li a1, %[c]\n"
".insn 0x00100073\n"
: : [c] "r" (code) : "a0", "a1"
);
}
void _start(void) {
// Write PASS via SYS_WRITE0
const char *msg = "PASS\n";
__asm__ volatile (
"li a0, 0x04\n"
"mv a1, %[m]\n"
".insn 0x00100073\n"
: : [m] "r" (msg) : "a0", "a1"
);
semihosting_exit(0);
while (1);
}
"#
.to_string()
}
pub fn generate_linker_script(&self, arch: EmbeddedArch) -> String {
let ls = LinkerScript::new(arch);
ls.generate()
}
pub fn run_test(&self, elf_path: &str) -> TestResult {
let result = self.runner.run(elf_path);
let mut passed = result.passed();
for expected in &self.expected_output {
if !result.contains_output(expected) {
passed = false;
break;
}
}
TestResult {
name: self.test_name.clone(),
passed,
output: result.stdout.clone(),
error: if passed { String::new() } else { result.stderr.clone() },
}
}
}
#[derive(Debug, Clone)]
pub struct UartTest {
pub machine: QemuMachine,
pub uart_base: u64,
pub test_name: String,
pub baud_rate: u32,
}
impl UartTest {
pub fn new(machine: QemuMachine, uart_base: u64, name: &str) -> Self {
Self {
machine,
uart_base,
test_name: name.to_string(),
baud_rate: 115200,
}
}
pub fn generate_cortex_m_uart_test(&self) -> String {
format!(
r#"// UART output test for Cortex-M
#include <stdint.h>
#define UART_BASE ((volatile uint32_t *){uart_base:#010x})
#define UART_DR (UART_BASE[0])
#define UART_FR (UART_BASE[6]) // Flag register
#define UART_IBRD (UART_BASE[9]) // Integer baud rate
#define UART_FBRD (UART_BASE[10]) // Fractional baud rate
#define UART_LCRH (UART_BASE[12]) // Line control
#define UART_CR (UART_BASE[13]) // Control register
void uart_init(void) {{
// Disable UART
UART_CR = 0;
// Set baud rate (assuming 24MHz clock)
uint32_t div = 24000000 / (16 * {baud});
UART_IBRD = div >> 6;
UART_FBRD = div & 0x3F;
// 8N1, FIFO enable
UART_LCRH = 0x70;
// Enable UART, TX, RX
UART_CR = 0x301;
}}
void uart_send(char c) {{
// Wait for TX FIFO not full
while (UART_FR & (1 << 5));
UART_DR = c;
}}
void uart_send_str(const char *s) {{
while (*s) {{
if (*s == '\n')
uart_send('\r');
uart_send(*s);
s++;
}}
}}
void _start(void) {{
uart_init();
uart_send_str("UART_OK\n");
// Exit via semihosting
__asm__ volatile (
"mov r0, #0x18\n"
"mov r1, #0\n"
"bkpt #0xAB\n"
);
while (1);
}}
"#,
uart_base = self.uart_base,
baud = self.baud_rate,
)
}
pub fn generate_riscv_uart_test(&self) -> String {
format!(
r#"// RISC-V NS16550A UART test
#include <stdint.h>
#define UART_BASE ((volatile uint8_t *){uart_base:#010x})
#define UART_THR (UART_BASE + 0x00) // Transmit holding register
#define UART_LSR (UART_BASE + 0x05) // Line status register
#define UART_LSR_THRE (1 << 5) // Transmit holding empty
void uart_send(char c) {{
while (!(*(volatile uint8_t *)UART_LSR & UART_LSR_THRE));
*(volatile uint8_t *)UART_THR = c;
}}
void _start(void) {{
const char *msg = "UART_OK\n";
while (*msg) {{
if (*msg == '\n')
uart_send('\r');
uart_send(*msg);
msg++;
}}
// Exit
__asm__ volatile (
"li a0, 0x18\n"
"li a1, 0\n"
".insn 0x00100073\n"
);
while (1);
}}
"#,
uart_base = self.uart_base,
)
}
pub fn build_runner(&self) -> QemuRunner {
QemuRunner::new(self.machine)
.with_uart_chardev(self.uart_base)
}
pub fn run_test(&self, elf_path: &str) -> TestResult {
let runner = self.build_runner();
let result = runner.run(elf_path);
TestResult {
name: self.test_name.clone(),
passed: result.passed() && result.contains_output("UART_OK"),
output: result.stdout.clone(),
error: result.stderr.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct InterruptTest {
pub arch: EmbeddedArch,
pub interrupt_name: String,
pub handler_executed: bool,
pub expected_count: u32,
}
impl InterruptTest {
pub fn new(arch: EmbeddedArch, irq_name: &str) -> Self {
Self {
arch,
interrupt_name: irq_name.to_string(),
handler_executed: false,
expected_count: 1,
}
}
pub fn generate_systick_test(&self) -> String {
r#"// SysTick interrupt test
#include <stdint.h>
volatile uint32_t systick_count = 0;
volatile uint32_t handler_called = 0;
void SysTick_Handler(void) {
systick_count++;
handler_called = 1;
}
// Default handler for other interrupts
void Default_Handler(void) {
while (1);
}
// Vector table
__attribute__((section(".vector_table")))
const uint32_t vector_table[] = {
[0] = 0x20010000, // Initial SP
[1] = (uint32_t)_start, // Reset
[2] = (uint32_t)Default_Handler, // NMI
[3] = (uint32_t)Default_Handler, // HardFault
[15] = (uint32_t)SysTick_Handler, // SysTick
};
void _start(void) {
// Configure SysTick for 1ms interval at ~24MHz
// SysTick->LOAD = 24000 - 1
*((volatile uint32_t *)0xE000E014) = 23999;
*((volatile uint32_t *)0xE000E018) = 0; // Clear current value
*((volatile uint32_t *)0xE000E010) = 0x7; // Enable with exception
// Wait for handler
for (int i = 0; i < 1000000; i++) {
if (handler_called) break;
}
// Disable SysTick
*((volatile uint32_t *)0xE000E010) = 0;
if (handler_called && systick_count >= 1) {
// PASS
__asm__ volatile (
"mov r0, #0x03\n"
"mov r1, #'P'\n"
"bkpt #0xAB\n"
);
}
// Exit
__asm__ volatile (
"mov r0, #0x18\n"
"mov r1, #0\n"
"bkpt #0xAB\n"
);
while (1);
}
"#
.to_string()
}
pub fn generate_riscv_timer_test(&self) -> String {
r#"// RISC-V Machine Timer Interrupt test
#include <stdint.h>
volatile uint32_t timer_irq_count = 0;
void trap_handler(uint32_t mcause, uint32_t mepc) {
if ((mcause & 0x7FFFFFFF) == 7) { // Machine timer interrupt
timer_irq_count++;
// Clear timer interrupt
volatile uint64_t *mtimecmp = (volatile uint64_t *)0x2004000;
*mtimecmp = *mtimecmp + 10000; // next tick
}
}
void _start(void) {
// Set up trap handler
__asm__ volatile (
"la t0, trap_entry\n"
"csrw mtvec, t0\n"
);
// Enable machine timer interrupt
__asm__ volatile (
"li t0, 0x80\n" // MIE bit for timer
"csrw mie, t0\n"
"csrw mstatus, 0x8\n" // MIE
);
// Wait for interrupt
for (int i = 0; i < 1000000; i++) {
if (timer_irq_count >= 1) break;
}
// Report result
if (timer_irq_count >= 1) {
const char *msg = "IRQ_OK\n";
__asm__ volatile (
"li a0, 0x04\n"
"mv a1, %[m]\n"
".insn 0x00100073\n"
: : [m] "r" (msg)
);
}
// Exit
__asm__ volatile (
"li a0, 0x18\n"
"li a1, 0\n"
".insn 0x00100073\n"
);
while (1);
}
// Trap entry in assembly
__asm__ (
".section .text.trap, \"ax\"\n"
".align 4\n"
"trap_entry:\n"
" csrr a0, mcause\n"
" csrr a1, mepc\n"
" call trap_handler\n"
" mret\n"
);
"#
.to_string()
}
pub fn run_test(&self, _elf_path: &str) -> TestResult {
TestResult {
name: self.interrupt_name.clone(),
passed: self.handler_executed,
output: String::new(),
error: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct StackOverflowTest {
pub arch: EmbeddedArch,
pub canary_value: u32,
pub stack_size: u32,
pub overflow_detected: bool,
}
impl StackOverflowTest {
pub fn new(arch: EmbeddedArch) -> Self {
Self {
arch,
canary_value: STACK_CANARY_VALUE,
stack_size: 1024,
overflow_detected: false,
}
}
pub fn generate_canary_test(&self) -> String {
format!(
r#"// Stack canary overflow detection test
#include <stdint.h>
#define CANARY_VALUE {canary:#010x}
// Place canary at top of stack
static volatile uint32_t __stack_canary __attribute__((section(".stack_canary"))) = CANARY_VALUE;
volatile uint32_t overflow_detected = 0;
void stack_overflow_handler(void) {{
overflow_detected = 1;
// Report and exit
}}
// Recursive function to trigger stack overflow
int recursive_depth(int n, volatile char *stack_marker) {{
char local[256]; // Large local to consume stack quickly
for (int i = 0; i < 256; i++)
local[i] = (char)(n + i);
if (n > 0) {{
return recursive_depth(n - 1, &local[0]);
}}
return (int)local[0];
}}
void _start(void) {{
// Trigger deep recursion to overflow stack
recursive_depth(100, (volatile char *)&__stack_canary);
// Check canary
if (__stack_canary != CANARY_VALUE) {{
stack_overflow_handler();
}}
// Report
if (overflow_detected) {{
__asm__ volatile (
"mov r0, #0x03\n"
"mov r1, #'S'\n"
"bkpt #0xAB\n"
);
}}
// Exit
__asm__ volatile (
"mov r0, #0x18\n"
"mov r1, #0\n"
"bkpt #0xAB\n"
);
while (1);
}}
"#,
canary = self.canary_value,
)
}
pub fn generate_mpu_guard_test(&self) -> String {
r#"// MPU guard region stack overflow test
#include <stdint.h>
void mpu_fault_handler(void) {
// Stack overflow detected by MPU
__asm__ volatile (
"mov r0, #0x03\n"
"mov r1, #'M'\n"
"bkpt #0xAB\n"
);
// Exit
__asm__ volatile (
"mov r0, #0x18\n"
"mov r1, #0\n"
"bkpt #0xAB\n"
);
while (1);
}
void _start(void) {
// Configure MPU guard region at stack bottom
// MPU region 7 as no-access
*((volatile uint32_t *)0xE000ED94) &= ~1; // Disable MPU
*((volatile uint32_t *)0xE000ED9C) = 7; // Select region 7
*((volatile uint32_t *)0xE000EDA0) = 0x20000000 | (1 << 4); // Base
*((volatile uint32_t *)0xE000EDA4) = 0x00000001 | (10 << 1); // 1KB, enable
*((volatile uint32_t *)0xE000ED94) |= (1 << 0) | (1 << 2); // Enable MPU
// Set MemManage fault handler in vector table
// ...
// Try to write below stack - should trigger MemManage fault
volatile uint32_t *below_stack = (volatile uint32_t *)0x1FFFFF00;
*below_stack = 0xDEAD; // This should fault
// If we get here, MPU didn't work
while (1);
}
"#
.to_string()
}
pub fn run_test(&self, _elf_path: &str) -> TestResult {
TestResult {
name: "stack_overflow".to_string(),
passed: self.overflow_detected,
output: String::new(),
error: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct StartupTest {
pub arch: EmbeddedArch,
pub bss_zeroed_ok: bool,
pub data_initialized_ok: bool,
pub main_called: bool,
}
impl StartupTest {
pub fn new(arch: EmbeddedArch) -> Self {
Self {
arch,
bss_zeroed_ok: false,
data_initialized_ok: false,
main_called: false,
}
}
pub fn generate_startup_test(&self) -> String {
r#"// Startup verification test: .bss zeroed, .data initialized, main called
#include <stdint.h>
// Variables in .bss (should be zeroed by startup)
static int bss_var __attribute__((section(".bss.bss_test")));
static char bss_buf[64] __attribute__((section(".bss.bss_test")));
// Variables in .data (should be initialized)
static int data_var __attribute__((section(".data.data_test"))) = 0x12345678;
static const char data_str[] __attribute__((section(".rodata"))) = "DATA_OK";
// Flag set by main
volatile int main_was_called = 0;
int main(void) {
main_was_called = 1;
// Check .bss is zeroed
int bss_ok = 1;
if (bss_var != 0) bss_ok = 0;
for (int i = 0; i < 64; i++) {
if (bss_buf[i] != 0) {
bss_ok = 0;
break;
}
}
// Check .data is initialized
int data_ok = (data_var == 0x12345678) ? 1 : 0;
// Report results via semihosting
if (bss_ok && data_ok && main_was_called) {
__asm__ volatile (
"mov r0, #0x03\n"
"mov r1, #'O'\n"
"bkpt #0xAB\n"
);
__asm__ volatile (
"mov r0, #0x03\n"
"mov r1, #'K'\n"
"bkpt #0xAB\n"
);
}
// Exit
__asm__ volatile (
"mov r0, #0x18\n"
"mov r1, #0\n"
"bkpt #0xAB\n"
);
return 0;
}
"#
.to_string()
}
pub fn run_test(&self, _elf_path: &str) -> TestResult {
TestResult {
name: "startup_verification".to_string(),
passed: self.bss_zeroed_ok && self.data_initialized_ok && self.main_called,
output: String::new(),
error: String::new(),
}
}
}
pub struct HelloWorldTest {
pub arch: EmbeddedArch,
pub message: String,
pub output_method: OutputMethod,
}
#[derive(Debug, Clone)]
pub enum OutputMethod {
Semihosting,
Uart(u64),
ItmSwo,
}
impl HelloWorldTest {
pub fn new(arch: EmbeddedArch) -> Self {
Self {
arch,
message: "Hello, Embedded World!\n".to_string(),
output_method: OutputMethod::Semihosting,
}
}
pub fn generate_hello_world(&self) -> String {
match self.arch.family() {
EmbeddedArchFamily::Arm => generate_arm_hello_world(&self.message),
EmbeddedArchFamily::RiscV => generate_riscv_hello_world(&self.message),
EmbeddedArchFamily::Avr => generate_avr_hello_world(&self.message),
_ => generate_arm_hello_world(&self.message),
}
}
pub fn minimal_vector_table_asm(&self) -> String {
r#" .syntax unified
.cpu cortex-m4
.thumb
.section .vector_table, "a"
.global __vector_table
__vector_table:
.word 0x20010000 // Initial SP
.word _start // Reset handler
.word default_handler // NMI
.word default_handler // HardFault
.word default_handler // MemManage
.word default_handler // BusFault
.word default_handler // UsageFault
.word 0 // Reserved
.word 0
.word 0
.word 0
.word default_handler // SVCall
.word default_handler // DebugMon
.word 0
.word default_handler // PendSV
.word default_handler // SysTick
.rept 240
.word default_handler
.endr
.section .text.default_handler, "ax"
.thumb_func
default_handler:
b .
.section .text._start, "ax"
.global _start
.thumb_func
_start:
// Load message address and call semihosting write0
ldr r0, =hello_msg
mov r1, #0x04 // SYS_WRITE0
bkpt #0xAB
// Exit
mov r0, #0x18 // SYS_EXIT
mov r1, #0
bkpt #0xAB
b .
.section .rodata
hello_msg:
.asciz "Hello, Embedded World!\n"
"#
.to_string()
}
pub fn qemu_runner(&self) -> QemuRunner {
let machine = match self.arch {
EmbeddedArch::ArmCortexM0 | EmbeddedArch::ArmCortexM3 => QemuMachine::Lm3s6965evb,
EmbeddedArch::ArmCortexM4 => QemuMachine::Stm32f4Discovery,
EmbeddedArch::ArmCortexM33 => QemuMachine::Mps2An505,
EmbeddedArch::Riscv32Imac => QemuMachine::RiscvVirt,
EmbeddedArch::Avr => QemuMachine::Atmega328p,
_ => QemuMachine::Lm3s6965evb,
};
QemuRunner::new(machine)
}
pub fn run_test(&self, elf_path: &str) -> TestResult {
let runner = self.qemu_runner();
let result = runner.run(elf_path);
TestResult {
name: "hello_world".to_string(),
passed: result.passed(),
output: result.stdout.clone(),
error: result.stderr.clone(),
}
}
}
fn generate_arm_hello_world(msg: &str) -> String {
format!(
r#"// ARM Bare-metal Hello World
const char hello_msg[] = "{}";
void _start(void) {{
// Write via semihosting
const char *p = hello_msg;
while (*p) {{
__asm__ volatile (
"mov r0, #0x03\n" // SYS_WRITEC
"mov r1, %[c]\n"
"bkpt #0xAB\n"
: : [c] "r" (*p) : "r0", "r1"
);
p++;
}}
// Exit
__asm__ volatile (
"mov r0, #0x18\n"
"mov r1, #0\n"
"bkpt #0xAB\n"
);
while (1);
}}
"#,
msg
)
}
fn generate_riscv_hello_world(msg: &str) -> String {
format!(
r#"// RISC-V Bare-metal Hello World
const char hello_msg[] = "{}";
void _start(void) {{
// Write via semihosting SYS_WRITE0
__asm__ volatile (
"li a0, 0x04\n"
"la a1, hello_msg\n"
".insn 0x00100073\n"
);
// Exit
__asm__ volatile (
"li a0, 0x18\n"
"li a1, 0\n"
".insn 0x00100073\n"
);
while (1);
}}
"#,
msg
)
}
fn generate_avr_hello_world(msg: &str) -> String {
format!(
r#"// AVR Bare-metal Hello World
#include <avr/io.h>
const char hello_msg[] PROGMEM = "{}";
void uart_init(void) {{
// Set baud rate for 9600 at 16MHz
UBRR0H = 0;
UBRR0L = 103;
// Enable TX
UCSR0B = (1 << TXEN0);
// 8N1
UCSR0C = (1 << UCSZ01) | (1 << UCSZ00);
}}
void uart_send(char c) {{
while (!(UCSR0A & (1 << UDRE0)));
UDR0 = c;
}}
int main(void) {{
uart_init();
const char *p = hello_msg;
while (pgm_read_byte(p)) {{
uart_send(pgm_read_byte(p));
p++;
}}
while (1);
return 0;
}}
"#,
msg
)
}
#[derive(Debug, Clone)]
pub struct CortexMTest {
pub variant: EmbeddedArch,
pub features: Vec<String>,
}
impl CortexMTest {
pub fn new(variant: EmbeddedArch) -> Self {
let features = match variant {
EmbeddedArch::ArmCortexM0 => vec!["Thumb-1".to_string()],
EmbeddedArch::ArmCortexM0Plus => vec!["Thumb-1".to_string(), "MPU".to_string()],
EmbeddedArch::ArmCortexM3 => vec!["Thumb-2".to_string(), "NVIC".to_string()],
EmbeddedArch::ArmCortexM4 => vec![
"Thumb-2".to_string(),
"DSP".to_string(),
"FPv4-SP".to_string(),
],
EmbeddedArch::ArmCortexM7 => vec![
"Thumb-2".to_string(),
"DSP".to_string(),
"FPv5".to_string(),
"Cache".to_string(),
],
EmbeddedArch::ArmCortexM23 => vec!["Thumb-1".to_string(), "TrustZone".to_string()],
EmbeddedArch::ArmCortexM33 => vec![
"Thumb-2".to_string(),
"TrustZone".to_string(),
"DSP".to_string(),
"FPv5".to_string(),
],
_ => vec!["Unknown".to_string()],
};
Self { variant, features }
}
pub fn compile_flags(&self) -> Vec<String> {
let mut flags = vec![
format!("--target={}", self.variant.target_triple()),
format!("-mcpu={}", self.variant.cpu_flag()),
"-mthumb".to_string(),
"-ffreestanding".to_string(),
"-nostdlib".to_string(),
];
if self.features.contains(&"FPv4-SP".to_string()) {
flags.push("-mfloat-abi=hard".to_string());
flags.push("-mfpu=fpv4-sp-d16".to_string());
}
if self.features.contains(&"FPv5".to_string()) {
flags.push("-mfloat-abi=hard".to_string());
flags.push("-mfpu=fpv5-d16".to_string());
}
flags
}
pub fn test_dsp_instructions(&self) -> &'static str {
r#"// DSP instruction test (Cortex-M4/M7)
#include <stdint.h>
#include <arm_math.h>
void _start(void) {
int32_t a = 100, b = 200;
// SMLAL — signed multiply accumulate long
int64_t result = (int64_t)a * b;
if (result == 20000) {
// PASS
__asm__ volatile (
"mov r0, #0x03\n"
"mov r1, #'D'\n"
"bkpt #0xAB\n"
);
}
// Exit
__asm__ volatile (
"mov r0, #0x18\n"
"mov r1, #0\n"
"bkpt #0xAB\n"
);
while (1);
}
"#
}
pub fn test_fpu_operations(&self) -> &'static str {
r#"// FPU test (Cortex-M4F/M7)
#include <stdint.h>
float add_floats(float a, float b) {
return a + b;
}
float mul_floats(float a, float b) {
return a * b;
}
void _start(void) {
float x = 1.5f, y = 2.5f;
float sum = add_floats(x, y);
float prod = mul_floats(x, y);
// 1.5 + 2.5 = 4.0, 1.5 * 2.5 = 3.75
int ok = (sum > 3.99f && sum < 4.01f) && (prod > 3.74f && prod < 3.76f);
if (ok) {
__asm__ volatile (
"mov r0, #0x03\n"
"mov r1, #'F'\n"
"bkpt #0xAB\n"
);
}
// Exit
__asm__ volatile (
"mov r0, #0x18\n"
"mov r1, #0\n"
"bkpt #0xAB\n"
);
while (1);
}
"#
}
}
#[derive(Debug, Clone)]
pub struct RiscVEmbeddedTest {
pub extensions: Vec<String>,
pub machine: QemuMachine,
}
impl RiscVEmbeddedTest {
pub fn rv32imac() -> Self {
Self {
extensions: vec![
"I".to_string(), "M".to_string(), "A".to_string(),
"C".to_string(),
],
machine: QemuMachine::RiscvVirt,
}
}
pub fn compile_flags(&self) -> Vec<String> {
let arch = format!("rv32{}", self.extensions.join(""));
vec![
format!("--target=riscv32-unknown-elf"),
format!("-march={}", arch),
"-mabi=ilp32".to_string(),
"-ffreestanding".to_string(),
"-nostdlib".to_string(),
]
}
pub fn test_m_extension(&self) -> &'static str {
r#"// RISC-V M extension test
#include <stdint.h>
void _start(void) {
int32_t a = 123, b = 456;
int64_t result = (int64_t)a * b;
// 123 * 456 = 56088
if (result == 56088) {
const char *msg = "M_OK\n";
__asm__ volatile (
"li a0, 0x04\n"
"mv a1, %[m]\n"
".insn 0x00100073\n"
: : [m] "r" (msg)
);
}
// Exit
__asm__ volatile (
"li a0, 0x18\n"
"li a1, 0\n"
".insn 0x00100073\n"
);
while (1);
}
"#
}
pub fn test_a_extension(&self) -> &'static str {
r#"// RISC-V A extension test
#include <stdint.h>
volatile int32_t atomic_var = 0;
void _start(void) {
int32_t expected = 0;
__asm__ volatile (
"amoadd.w %[result], %[inc], (%[addr])\n"
: [result] "=r" (expected)
: [inc] "r" (42), [addr] "r" (&atomic_var)
);
if (atomic_var == 42 && expected == 0) {
const char *msg = "A_OK\n";
__asm__ volatile (
"li a0, 0x04\n"
"mv a1, %[m]\n"
".insn 0x00100073\n"
: : [m] "r" (msg)
);
}
// Exit
__asm__ volatile (
"li a0, 0x18\n"
"li a1, 0\n"
".insn 0x00100073\n"
);
while (1);
}
"#
}
pub fn test_c_extension(&self) -> &'static str {
r#"// RISC-V C extension test
// Verify code size is reduced with compressed instructions
int simple_add(int a, int b) {
return a + b; // Should use c.add
}
int simple_li(void) {
return 5; // Should use c.li
}
void _start(void) {
int r1 = simple_add(1, 2);
int r2 = simple_li();
if (r1 == 3 && r2 == 5) {
const char *msg = "C_OK\n";
__asm__ volatile (
"li a0, 0x04\n"
"mv a1, %[m]\n"
".insn 0x00100073\n"
: : [m] "r" (msg)
);
}
// Exit
__asm__ volatile (
"li a0, 0x18\n"
"li a1, 0\n"
".insn 0x00100073\n"
);
while (1);
}
"#
}
}
#[derive(Debug, Clone)]
pub struct AvrTest {
pub mcu: String,
pub flash_size: u32,
pub ram_size: u32,
}
impl AvrTest {
pub fn atmega328p() -> Self {
Self {
mcu: "atmega328p".to_string(),
flash_size: 32768,
ram_size: 2048,
}
}
pub fn compile_flags(&self) -> Vec<String> {
vec![
format!("-mmcu={}", self.mcu),
"--target=avr-unknown-unknown".to_string(),
"-ffreestanding".to_string(),
"-nostdlib".to_string(),
]
}
pub fn generate_simavr_test(&self) -> String {
format!(
r#"// AVR test for {}
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint8_t test_passed = 0;
// Timer0 overflow interrupt
ISR(TIMER0_OVF_vect) {{
test_passed = 1;
}}
int main(void) {{
// Configure Timer0 with prescaler
TCCR0B = (1 << CS00) | (1 << CS02); // clk/1024
// Enable Timer0 overflow interrupt
TIMSK0 = (1 << TOIE0);
sei();
// Wait for interrupt
uint16_t timeout = 0;
while (!test_passed && timeout < 0xFFFF) {{
timeout++;
}}
if (test_passed) {{
// Set PORTB as output and toggle pin for verification
DDRB = 0xFF;
PORTB = 0xAA;
}}
while (1);
return 0;
}}
"#,
self.mcu
)
}
}
#[derive(Debug, Clone)]
pub struct Msp430Test {
pub mcu: String,
}
impl Msp430Test {
pub fn msp430fr5969() -> Self {
Self {
mcu: "msp430fr5969".to_string(),
}
}
pub fn compile_flags(&self) -> Vec<String> {
vec![
format!("-mmcu={}", self.mcu),
"--target=msp430-none-elf".to_string(),
"-ffreestanding".to_string(),
"-nostdlib".to_string(),
]
}
pub fn generate_mspdebug_test(&self) -> String {
format!(
r#"// MSP430 test for {}
#include <msp430.h>
volatile unsigned int test_result = 0;
int main(void) {{
// Stop watchdog timer
WDTCTL = WDTPW | WDTHOLD;
// Test basic arithmetic
unsigned int a = 100;
unsigned int b = 200;
unsigned int sum = a + b;
if (sum == 300) {{
test_result = 1;
}}
// Configure GPIO
P1DIR |= BIT0; // P1.0 output
P1OUT |= BIT0; // Set P1.0 high
// Low power mode
__bis_SR_register(LPM4_bits);
return 0;
}}
"#,
self.mcu
)
}
}
#[derive(Debug, Clone)]
pub struct TestResult {
pub name: String,
pub passed: bool,
pub output: String,
pub error: String,
}
impl TestResult {
pub fn new(name: &str, passed: bool) -> Self {
Self {
name: name.to_string(),
passed,
output: String::new(),
error: String::new(),
}
}
pub fn with_output(mut self, output: &str) -> Self {
self.output = output.to_string();
self
}
pub fn with_error(mut self, error: &str) -> Self {
self.error = error.to_string();
self
}
}
impl fmt::Display for TestResult {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let status = if self.passed { "PASS" } else { "FAIL" };
write!(f, "[{}] {}", status, self.name)
}
}
#[derive(Debug, Clone)]
pub struct EmbeddedTestSuite {
pub name: String,
pub tests: Vec<TestResult>,
pub elf_binary: Option<String>,
pub arch: EmbeddedArch,
pub qemu_machine: QemuMachine,
}
impl EmbeddedTestSuite {
pub fn new(name: &str, arch: EmbeddedArch, machine: QemuMachine) -> Self {
Self {
name: name.to_string(),
tests: Vec::new(),
elf_binary: None,
arch,
qemu_machine: machine,
}
}
pub fn add_result(&mut self, result: TestResult) {
self.tests.push(result);
}
pub fn run_all(&mut self) {
}
pub fn passed_count(&self) -> usize {
self.tests.iter().filter(|t| t.passed).count()
}
pub fn failed_count(&self) -> usize {
self.tests.iter().filter(|t| !t.passed).count()
}
pub fn total(&self) -> usize {
self.tests.len()
}
pub fn summary(&self) -> String {
let mut report = String::new();
report.push_str(&format!("Test Suite: {}\n", self.name));
report.push_str(&format!("Architecture: {:?}\n", self.arch));
report.push_str(&format!("Machine: {:?}\n", self.qemu_machine));
report.push_str(&format!(
"Results: {}/{} passed\n",
self.passed_count(),
self.total()
));
for test in &self.tests {
report.push_str(&format!(" {}\n", test));
}
report
}
}
impl Default for EmbeddedTestSuite {
fn default() -> Self {
Self::new(
"default",
EmbeddedArch::ArmCortexM4,
QemuMachine::Stm32f4Discovery,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qemu_machine_names() {
assert_eq!(QemuMachine::Lm3s6965evb.machine_name(), "lm3s6965evb");
assert_eq!(QemuMachine::RiscvVirt.machine_name(), "virt");
assert_eq!(QemuMachine::Atmega328p.machine_name(), "atmega328p");
}
#[test]
fn test_qemu_machine_binaries() {
assert_eq!(QemuMachine::Lm3s6965evb.qemu_binary(), "qemu-system-arm");
assert_eq!(QemuMachine::RiscvVirt.qemu_binary(), "qemu-system-riscv32");
assert_eq!(QemuMachine::Atmega328p.qemu_binary(), "qemu-system-avr");
assert_eq!(
QemuMachine::Msp430fr5969.qemu_binary(),
"qemu-system-msp430"
);
}
#[test]
fn test_qemu_runner_command() {
let runner = QemuRunner::new(QemuMachine::Lm3s6965evb);
let cmd = runner.build_command("test.elf");
assert!(cmd.contains(&"-M".to_string()));
assert!(cmd.contains(&"lm3s6965evb".to_string()));
assert!(cmd.contains(&"-semihosting".to_string()));
assert!(cmd.contains(&"test.elf".to_string()));
}
#[test]
fn test_qemu_runner_shell_command() {
let runner = QemuRunner::new(QemuMachine::RiscvVirt);
let shell = runner.shell_command("test.elf");
assert!(shell.contains("qemu-system-riscv32"));
assert!(shell.contains("test.elf"));
}
#[test]
fn test_qemu_run_result_passed() {
let result = QemuRunResult {
command: String::new(),
exit_code: 0,
stdout: "PASS\n".to_string(),
stderr: String::new(),
timed_out: false,
elapsed_ms: 100,
};
assert!(result.passed());
assert!(result.contains_output("PASS"));
}
#[test]
fn test_qemu_run_result_failed() {
let result = QemuRunResult {
command: String::new(),
exit_code: 1,
stdout: String::new(),
stderr: "error".to_string(),
timed_out: true,
elapsed_ms: 30000,
};
assert!(!result.passed());
assert!(!result.contains_output("PASS"));
}
#[test]
fn test_semihosting_test_pass() {
let mut test = SemihostingTest::new(QemuMachine::Lm3s6965evb, "semihosting_pass");
test.expect_pass();
assert_eq!(test.expected_output.len(), 1);
assert_eq!(test.expected_output[0], "PASS\n");
}
#[test]
fn test_semihosting_test_generate_c() {
let test = SemihostingTest::new(QemuMachine::Lm3s6965evb, "test");
let c = test.generate_pass_test_c();
assert!(c.contains("_start"));
assert!(c.contains("SYS_WRITEC"));
assert!(c.contains("bkpt #0xAB"));
}
#[test]
fn test_semihosting_riscv_test() {
let test = SemihostingTest::new(QemuMachine::RiscvVirt, "riscv_test");
let c = test.generate_riscv_pass_test_c();
assert!(c.contains("_start"));
assert!(c.contains("0x00100073"));
}
#[test]
fn test_uart_test_cortex_m() {
let test = UartTest::new(QemuMachine::Stm32f4Discovery, 0x40011000, "uart_test");
let c = test.generate_cortex_m_uart_test();
assert!(c.contains("UART_BASE"));
assert!(c.contains("UART_OK"));
assert!(c.contains("uart_init"));
}
#[test]
fn test_uart_test_riscv() {
let test = UartTest::new(QemuMachine::RiscvVirt, 0x10000000, "riscv_uart");
let c = test.generate_riscv_uart_test();
assert!(c.contains("NS16550A"));
assert!(c.contains("UART_OK"));
}
#[test]
fn test_interrupt_test_systick() {
let test = InterruptTest::new(EmbeddedArch::ArmCortexM4, "SysTick");
let c = test.generate_systick_test();
assert!(c.contains("SysTick_Handler"));
assert!(c.contains("vector_table"));
}
#[test]
fn test_interrupt_test_riscv_timer() {
let test = InterruptTest::new(EmbeddedArch::Riscv32Imac, "Timer");
let c = test.generate_riscv_timer_test();
assert!(c.contains("trap_handler"));
assert!(c.contains("mcause"));
}
#[test]
fn test_stack_overflow_canary() {
let test = StackOverflowTest::new(EmbeddedArch::ArmCortexM4);
let c = test.generate_canary_test();
assert!(c.contains("CANARY_VALUE"));
assert!(c.contains("stack_canary"));
assert!(c.contains("recursive_depth"));
}
#[test]
fn test_stack_overflow_mpu() {
let test = StackOverflowTest::new(EmbeddedArch::ArmCortexM4);
let c = test.generate_mpu_guard_test();
assert!(c.contains("mpu_fault_handler"));
assert!(c.contains("MPU"));
}
#[test]
fn test_startup_verification() {
let test = StartupTest::new(EmbeddedArch::ArmCortexM4);
let c = test.generate_startup_test();
assert!(c.contains(".bss"));
assert!(c.contains(".data"));
assert!(c.contains("main_was_called"));
}
#[test]
fn test_hello_world_arm() {
let test = HelloWorldTest::new(EmbeddedArch::ArmCortexM4);
let c = test.generate_hello_world();
assert!(c.contains("hello_msg"));
assert!(c.contains("_start"));
}
#[test]
fn test_hello_world_riscv() {
let test = HelloWorldTest::new(EmbeddedArch::Riscv32Imac);
let c = test.generate_hello_world();
assert!(c.contains("hello_msg"));
assert!(c.contains("_start"));
}
#[test]
fn test_cortex_m_compile_flags() {
let test = CortexMTest::new(EmbeddedArch::ArmCortexM4);
let flags = test.compile_flags();
assert!(flags.iter().any(|f| f.contains("cortex-m4")));
assert!(flags.iter().any(|f| f.contains("fpv4-sp-d16")));
}
#[test]
fn test_cortex_m_dsp_test() {
let test = CortexMTest::new(EmbeddedArch::ArmCortexM4);
let c = test.test_dsp_instructions();
assert!(c.contains("SMLAL"));
assert!(c.contains("20000"));
}
#[test]
fn test_cortex_m_fpu_test() {
let test = CortexMTest::new(EmbeddedArch::ArmCortexM4);
let c = test.test_fpu_operations();
assert!(c.contains("add_floats"));
assert!(c.contains("mul_floats"));
assert!(c.contains("1.5"));
}
#[test]
fn test_riscv_m_extension_test() {
let test = RiscVEmbeddedTest::rv32imac();
let c = test.test_m_extension();
assert!(c.contains("56088"));
assert!(c.contains("M_OK"));
}
#[test]
fn test_riscv_a_extension_test() {
let test = RiscVEmbeddedTest::rv32imac();
let c = test.test_a_extension();
assert!(c.contains("amoadd"));
assert!(c.contains("A_OK"));
}
#[test]
fn test_riscv_c_extension_test() {
let test = RiscVEmbeddedTest::rv32imac();
let c = test.test_c_extension();
assert!(c.contains("simple_add"));
assert!(c.contains("C_OK"));
}
#[test]
fn test_avr_generate_test() {
let test = AvrTest::atmega328p();
let c = test.generate_simavr_test();
assert!(c.contains("TIMER0_OVF"));
assert!(c.contains("PORTB"));
}
#[test]
fn test_avr_compile_flags() {
let test = AvrTest::atmega328p();
let flags = test.compile_flags();
assert!(flags.iter().any(|f| f.contains("atmega328p")));
}
#[test]
fn test_msp430_generate_test() {
let test = Msp430Test::msp430fr5969();
let c = test.generate_mspdebug_test();
assert!(c.contains("WDTCTL"));
assert!(c.contains("LPM4_bits"));
}
#[test]
fn test_msp430_compile_flags() {
let test = Msp430Test::msp430fr5969();
let flags = test.compile_flags();
assert!(flags.iter().any(|f| f.contains("msp430fr5969")));
assert!(flags.iter().any(|f| f.contains("msp430")));
}
#[test]
fn test_embedded_test_suite() {
let mut suite = EmbeddedTestSuite::new(
"cortex_m4_tests",
EmbeddedArch::ArmCortexM4,
QemuMachine::Stm32f4Discovery,
);
suite.add_result(TestResult::new("test1", true));
suite.add_result(TestResult::new("test2", false));
suite.add_result(TestResult::new("test3", true));
assert_eq!(suite.passed_count(), 2);
assert_eq!(suite.failed_count(), 1);
assert_eq!(suite.total(), 3);
let summary = suite.summary();
assert!(summary.contains("2/3 passed"));
}
#[test]
fn test_test_result_display() {
let pass = TestResult::new("pass_test", true);
let fail = TestResult::new("fail_test", false);
assert!(format!("{}", pass).contains("PASS"));
assert!(format!("{}", fail).contains("FAIL"));
}
#[test]
fn test_hello_world_minimal_asm() {
let test = HelloWorldTest::new(EmbeddedArch::ArmCortexM4);
let asm = test.minimal_vector_table_asm();
assert!(asm.contains("vector_table"));
assert!(asm.contains("_start"));
assert!(asm.contains("hello_msg"));
}
}