第二章:平台搭建——创建第一个虚拟平台

好,咱们正式开始动手。上一章聊了 Renode 能干什么,这一章我们就来搭一个能跑起来的虚拟平台。说白了,就是让 Renode 认识你的硬件——CPU 是什么、外设挂在哪、内存多大。

我个人习惯,每开始一个新项目,第一件事就是写平台描述文件。这玩意儿后缀是 .repl,全称 Renode Platform Description Language。你把它当成硬件原理图的文本版就行。

2.1 平台描述文件(.repl)长什么样?

先看一个最简单的例子。假设我们要模拟一个 STM32F103 级别的 Cortex-M3 芯片,最小系统长这样:

cpu: CPU.CortexM @ sysbus
    frequency: 72000000
    init:
        VectorTableOffset: 0x00000000

flash: Memory.MappedMemory @ sysbus 0x00000000
    size: 0x00100000

ram: Memory.MappedMemory @ sysbus 0x20000000
    size: 0x00020000

uart: UART.STM32F4_USART @ sysbus 0x40013800
    -> cpu@0x00000053

这段代码干了三件事:

  • 声明 CPU:Cortex-M3 内核,主频 72MHz,中断向量表从 0x0 开始。
  • 挂载内存:Flash 1MB 在 0x00000000,RAM 128KB 在 0x20000000。
  • 挂载外设:UART 寄存器基址 0x40013800,中断号 0x53(即 IRQ 83)。

注意那个 @ sysbus 的写法。sysbus 是 Renode 内部的系统总线,所有挂载在它下面的设备,地址空间都是全局可见的。你想想看,这跟真实芯片的 AHB/APB 总线是一个道理。

小技巧:-> cpu@0x00000053 表示这个外设的中断连接到 CPU 的 0x53 号中断线。这个编号必须跟芯片手册的 NVIC 中断号一致。我刚开始写的时候经常搞错,后来养成了先查手册再写 repl 的习惯。

2.2 外设挂载的两种方式

实际项目中,外设挂载有两种常见写法。我列个表对比一下:

方式 语法 适用场景
直接挂载 UART.STM32F4_USART @ sysbus 0x40013800 外设直接挂在系统总线上,地址固定
桥接挂载 apb1: Bus.BusBridge @ sysbus 0x40000000
-> uart: UART.STM32F4_USART @ apb1 0x00003800
外设在桥接总线后面,地址需要偏移

直接挂载最简单,但真实芯片很少这么干。STM32 的 APB1 总线基址是 0x40000000,UART2 的偏移是 0x00003800,所以实际地址是 0x40003800。用桥接挂载能更真实地模拟总线拓扑。

避坑指南:我曾经在做一个多外设项目时,把所有外设都直接挂到 sysbus 上,结果地址冲突了都没发现。Renode 启动时不报错,但外设读写行为完全不对。后来排查了两天才找到原因——两个外设的地址范围重叠了。所以,一定要用桥接挂载来模拟真实的总线层级,这样地址管理清晰得多。

2.3 外设配置的细节

外设挂上去之后,还得配参数。比如 UART 的波特率、GPIO 的初始电平、定时器的预分频值。这些配置写在 init: 块里:

uart: UART.STM32F4_USART @ sysbus 0x40013800
    init:
        BaudRate: 115200
        Parity: None
        StopBits: 1
        DataBits: 8

嗯,这里要注意:init: 块里的参数是 Renode 启动时一次性写入的,不是运行时动态配置。如果你想模拟驱动初始化过程,那得在测试脚本里写。

我个人习惯,把外设的默认配置写在 repl 里,这样每次启动环境都是一致的。测试驱动时,再通过故障注入来改变这些配置,看看驱动能不能正确处理异常情况。

2.4 完整的平台描述文件示例

下面是一个稍微完整点的例子,模拟了一个带 GPIO、UART、Timer 的 Cortex-M4 平台:

cpu: CPU.CortexM @ sysbus
    frequency: 168000000
    init:
        VectorTableOffset: 0x08000000

flash: Memory.MappedMemory @ sysbus 0x08000000
    size: 0x00200000

ram: Memory.MappedMemory @ sysbus 0x20000000
    size: 0x00040000

gpioa: GPIO.STM32F4_GPIO @ sysbus 0x40020000
    init:
        Mode: [0, 1, 2, 3, 4, 5, 6, 7] -> Output
        OutputType: PushPull

uart1: UART.STM32F4_USART @ sysbus 0x40011000
    init:
        BaudRate: 115200
    -> cpu@0x00000037

timer2: Timer.STM32F4_TIM @ sysbus 0x40000000
    init:
        Prescaler: 168
        Period: 1000
    -> cpu@0x0000001C

这个平台有 2MB Flash、256KB RAM、一组 GPIO、一个 UART、一个定时器。够跑一个简单的 RTOS 或者裸机程序了。

2.5 知识结构图

下面这张图总结了平台描述文件的核心组成和挂载流程:

平台描述文件(.repl)核心结构 sysbus(系统总线) CPU.CortexM Memory.MappedMemory 外设(UART/GPIO/TIM) init: 配置参数 -> cpu@中断号 说明: • 所有设备通过 @ 符号挂载到 sysbus 或桥接总线上 • init 块配置启动参数,中断连接用 -> cpu@ 语法

2.6 启动你的第一个虚拟平台

文件写好了,怎么跑?很简单,在 Renode 命令行里执行:

(monitor) mach create "my_board"
(machine-0) machine LoadPlatformDescription @/path/to/my_board.repl
(machine-0) sysbus.cpu StartGdbServer 3333
(machine-0) sysbus.cpu ExecutionMode SingleStep

这三条命令的意思是:

  1. 创建一个名为 my_board 的虚拟机实例。
  2. 加载我们写的平台描述文件。
  3. 启动 GDB 服务器,端口 3333,方便调试。

然后你就可以用 arm-none-eabi-gdb 连接上去,下载固件、设断点、单步执行了。跟真芯片调试一模一样。

核心要点:平台描述文件是 Renode 虚拟硬件的“蓝图”。你写得越细致,模拟就越真实。但刚开始不用追求完美,先让 CPU 跑起来,再逐步添加外设和配置。

2.7 常见问题与排查

我遇到过不少新手在第一步就卡住。这里列几个常见问题:

  • 平台加载失败:检查 repl 文件路径是否正确,语法有没有拼写错误。Renode 的报错信息还算友好,仔细看。
  • CPU 不执行代码:确认 VectorTableOffset 指向了正确的 Flash 地址。如果固件是 0x08000000 开始的,但 VectorTableOffset 设成了 0x00000000,CPU 会直接跑飞。
  • 外设寄存器读不到:检查地址是否跟芯片手册一致。我曾经把 UART 的基址写错了一位,结果读出来的数据全是 0xFF。