欢迎光临
我们一直在努力

嵌入式软件单元测试(四十四)——RISC-V架构MCU单元测试:从工具链搭建到第一个测试用例

❄️ 我的个人专栏: 
《智能软件工程AI4SE》
《嵌入式面试总结》
《嵌入式处理器架构解析》
《嵌入式与虚拟化》
《嵌入式软件测试》
🌟 Simplicity is the ultimate sophistication

摘要:本文聚焦RISC-V架构MCU的单元测试实践,首先分析了RISC-V在工具链碎片化、指令集扩展差异和调试接口多样性三方面带来的测试特殊性,随后以Ubuntu 22.04为例,详细演示了RISC-V GCC交叉编译工具链、QEMU模拟器以及CMake与Unity测试框架的搭建流程。接着通过一个LED控制模块的完整示例,从被测模块源码、测试用例编写、CMake构建脚本到编译运行,逐步引导读者完成第一个可运行的测试用例,并针对链接入口、浮点支持不匹配和内存布局等常见问题给出了排查思路。

1. 引言

随着RISC-V架构在嵌入式领域的快速普及,越来越多的MCU产品开始采用这一开放指令集架构。从开发者的视角来看,RISC-V带来的不仅是指令集的开源自由,还有工具链选择的多样性。然而,这种多样性也带来了新的挑战——如何在RISC-V架构下高效开展单元测试,成为许多嵌入式团队必须面对的问题。

本文作为嵌入式软件单元测试系列的第44篇,将聚焦RISC-V架构MCU的单元测试实践,从工具链搭建开始,逐步引导读者完成第一个可运行的测试用例。

2. RISC-V架构MCU单元测试的特殊性

与传统的ARM Cortex-M系列MCU相比,RISC-V架构MCU的单元测试存在几个显著差异,理解这些差异是搭建测试环境的前提。

2.1 工具链的碎片化

ARM生态经过多年发展,已经形成了以armcc、arm-none-eabi-gcc等为核心的成熟工具链体系。而RISC-V的工具链则呈现碎片化特征:官方维护的riscv-gnu-toolchain、各芯片厂商提供的定制版本、以及第三方商业工具链并存。这种碎片化直接影响了单元测试的编译、链接和运行方式。

2.2 指令集扩展的差异

RISC-V采用模块化指令集设计,不同MCU可能支持不同的扩展组合。例如,有的芯片支持M扩展(整数乘除法),有的支持F扩展(单精度浮点),还有的支持C扩展(压缩指令)。单元测试代码在编译时,必须针对目标芯片的指令集扩展进行正确配置,否则可能出现指令非法或运行异常。

2.3 调试接口的多样性

ARM Cortex-M普遍采用SWD或JTAG调试接口,调试工具链相对统一。RISC-V MCU则可能使用JTAG、cJTAG或自定义调试接口,调试器厂商也各不相同。这导致单元测试的覆盖率采集、断点设置等操作在不同平台上表现不一致。

3. 工具链搭建

本节将介绍如何在Linux环境下搭建RISC-V MCU单元测试所需的完整工具链。以下操作以Ubuntu 22.04 LTS为例。

3.1 安装RISC-V GCC交叉编译工具链

推荐使用官方维护的riscv-gnu-toolchain,它提供了完整的GCC、Binutils和GDB支持。可以通过源码编译安装,也可以直接使用发行版预编译包。

# 安装依赖
sudo apt update
sudo apt install -y autoconf automake autotools-dev curl python3 python3-pip \\
libmpc-dev libmpfr-dev libgmp-dev gawk build-essential bison flex \\
texinfo gperf libtool patchutils bc zlib1g-dev libexpat-dev
克隆工具链源码
git clone –recursive https://github.com/riscv-collab/riscv-gnu-toolchain
cd riscv-gnu-toolchain
配置并编译(以裸机目标为例)
./configure –prefix=/opt/riscv –with-arch=rv32imac –with-abi=ilp32
make -j$(nproc)

编译完成后,将工具链路径加入环境变量:

export PATH=/opt/riscv/bin:$PATH

验证安装是否成功:

riscv32-unknown-elf-gcc –version

3.2 安装QEMU模拟器

单元测试通常需要在主机上快速运行,QEMU提供了RISC-V的系统级和用户级模拟能力,是运行测试用例的理想选择。

sudo apt install -y qemu-system-misc qemu-user
验证安装
qemu-riscv32 –version

3.3 安装CMake和测试框架

CMake用于构建管理,Unity是轻量级的C语言单元测试框架,非常适合嵌入式场景。

sudo apt install -y cmake
下载Unity测试框架
git clone https://github.com/ThrowTheSwitch/Unity.git
sudo cp -r Unity/src /opt/unity

4. 第一个测试用例

工具链就绪后,我们通过一个简单的示例来演示完整的单元测试流程。假设被测模块是一个LED控制模块,包含初始化、点亮和熄灭三个函数。

4.1 被测模块源码

创建被测模块头文件 led.h:

#ifndef LED_H
#define LED_H
#include <stdint.h>
void led_init(void);
void led_on(uint8_t pin);
void led_off(uint8_t pin);
#endif /* LED_H */

创建被测模块实现文件 led.c:

#include "led.h"
static uint8_t led_state[8] = {0};
void led_init(void)
{
for (int i = 0; i < 8; i++) {
led_state[i] = 0;
}
}
void led_on(uint8_t pin)
{
if (pin < 8) {
led_state[pin] = 1;
}
}
void led_off(uint8_t pin)
{
if (pin < 8) {
led_state[pin] = 0;
}
}

4.2 编写测试用例

创建测试文件 test_led.c:

#include "unity.h"
#include "led.h"
void setUp(void)
{
led_init();
}
void tearDown(void)
{
}
void test_led_init_clears_all_pins(void)
{
led_on(3);
led_init();
TEST_ASSERT_EQUAL_UINT8(0, led_get_state(3));
}
void test_led_on_sets_pin_state(void)
{
led_on(2);
TEST_ASSERT_EQUAL_UINT8(1, led_get_state(2));
}
void test_led_off_clears_pin_state(void)
{
led_on(5);
led_off(5);
TEST_ASSERT_EQUAL_UINT8(0, led_get_state(5));
}
void test_led_on_invalid_pin_ignored(void)
{
led_on(8);
TEST_ASSERT_EQUAL_UINT8(0, led_get_state(8));
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_led_init_clears_all_pins);
RUN_TEST(test_led_on_sets_pin_state);
RUN_TEST(test_led_off_clears_pin_state);
RUN_TEST(test_led_on_invalid_pin_ignored);
return UNITY_END();
}

注意,测试代码中引用了 led_get_state 函数,该函数用于读取内部状态,需要在被测模块中补充实现:

uint8_t led_get_state(uint8_t pin)
{
if (pin < 8) {
return led_state[pin];
}
return 0;
}

4.3 编写CMake构建脚本

创建 CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(led_test C)
set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(UNITY_DIR "/opt/unity")
set(TARGET "riscv32-unknown-elf")
编译被测模块和测试代码
add_executable(led_test
led.c
test_led.c
${UNITY_DIR}/unity.c
)
target_include_directories(led_test PRIVATE
${UNITY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
设置RISC-V编译选项
target_compile_options(led_test PRIVATE
-march=rv32imac
-mabi=ilp32
-ffunction-sections
-fdata-sections
-Wall
-Wextra
)
target_link_options(led_test PRIVATE
-march=rv32imac
-mabi=ilp32
-Wl,–gc-sections
)

4.4 编译并运行测试

mkdir build && cd build
cmake .. -DCMAKE_C_COMPILER=riscv32-unknown-elf-gcc
make
使用QEMU用户模式运行测试
qemu-riscv32 ./led_test

运行结果应显示所有测试通过:

test_led_init_clears_all_pins PASS
test_led_on_sets_pin_state PASS
test_led_off_clears_pin_state PASS
test_led_on_invalid_pin_ignored PASS
4 Tests 0 Failures 0 Ignored
OK

5. 常见问题与解决思路

在RISC-V MCU单元测试实践中,开发者常遇到以下几类问题,这里给出对应的排查思路。

5.1 链接错误:无法找到入口符号

裸机目标没有操作系统提供启动代码,链接时可能报错找不到 _start 或 main 入口。解决方法是使用QEMU用户模式运行测试,该模式会自动处理入口问题;如果必须使用系统模式,则需要提供启动文件。

5.2 浮点支持不匹配

如果目标芯片支持F扩展,但编译时未指定 -march=rv32imafc,浮点运算可能被编译为软浮点调用,导致性能下降或链接错误。务必确保编译选项与目标芯片的硬件特性一致。

5.3 内存布局问题

在系统模式下运行测试时,需要为测试程序配置正确的链接脚本,指定代码段、数据段和堆栈的地址范围。可以参考芯片厂商提供的链接脚本模板进行修改。

6. 总结

本文从RISC-V架构MCU单元测试的特殊性出发,详细介绍了工具链搭建的完整流程,并通过一个LED控制模块的示例,演示了从编写测试用例到编译运行的完整过程。RISC-V的开放性为嵌入式开发带来了更多选择,同时也要求开发者对工具链和指令集有更深入的理解。

在后续的系列文章中,我们将进一步探讨RISC-V架构下的覆盖率分析、模拟器高级用法以及CI集成等进阶话题,帮助读者构建更加完善的单元测试体系。

赞(0)
未经允许不得转载:171主机测评 » 嵌入式软件单元测试(四十四)——RISC-V架构MCU单元测试:从工具链搭建到第一个测试用例
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址