将Zephyr开发环境做为risc-v的实验环境

Creative Commons
本作品采用知识共享署名

本文说明如何利用Zephyr的的开发环境作为risc-v的实验环境,并做简单演示。

Zephyr支援risc-v的qemu,我们可以利用Zephyr SDK中risc-v的toolchain和qemu构造一个简单而纯净的环境,比起一开始就在Zephyr中去看risc-v的启动代码和链接脚本,更利于入门者理解和学习risc-v。本文在说明如何在WSL下建立该环境,该方法也使用其它Linux发行版。

工具安装

在安装全套Zephyr SDK情况下,那么可以在那么里面就已经包含了risc-v的toolchain和qemu,例如我安装在/mnt/e/westz/zephyr-sdk-0.11.4目录下,对应的toolchain和qemu的路径如下

1
2
/mnt/e/westz/zephyr-sdk-0.11.4/riscv64-zephyr-elf/bin/
/mnt/e/westz/zephyr-sdk-0.11.4/sysroots/x86_64-pokysdk-linux/usr/bin/

如果你已经安装了Zephyr SDK可以跳过本小结后面内容。
如果你没有安装Zephyr SDK可以只安装risc-v toolchain和qemu

下载

可以在https://github.com/zephyrproject-rtos/sdk-ng/releases找到最新的SDK,这里我们下载zephyr-sdk-x86_64-hosttools-standalone-0.9.sh(包含QEMU)和zephyr-toolchain-riscv64-0.12.0-x86_64-linux-setup.run(risc-v toolchain),两个链接如下

1
2
https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.12.0/zephyr-sdk-x86_64-hosttools-standalone-0.9.sh
https://github.com/zephyrproject-rtos/sdk-ng/releases/download/v0.12.0/zephyr-toolchain-riscv64-0.12.0-x86_64-linux-setup.run

安装

直接在shell中执行上面下载的两个文件,按照提示输入要安装的路径,然后安装提示选择即可完成。

起始完全可以下载risc-v toolchain和qemu的源代码进行编译,但既然Zephyr的SDK已经提供拿来用又那点不好呢。

调试环境搭建

了解qemu的启动命令

基本上调试环境的搭建是参考Zephyr的qemu_riscv32,参考qemu_riscv32对qemu的启动参数然后构建我们的环境,我们可以通过下面命令在qemu_riscv32上运行Zephyr进行debug

1
west -v build -b qemu_riscv32 -t debugserver westz/zephyrmaster/zephyr/samples/hello_world/

启动可以看到qemu的启动命令:

1
cd /mnt/e/build && /mnt/e/westz/zephyr-sdk-0.11.4/sysroots/x86_64-pokysdk-linux/usr/bin/qemu-system-riscv32 -nographic -machine sifive_e -net none -pidfile qemu.pid -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -icount shift=6,align=off,sleep=off -rtc clock=vm -s -S -kernel /mnt/e/build/zephyr/zephyr.elf

了解模拟硬件信息

基本上我们了解了ROM和RAM的信息就可以写程序了,所以查看用上面west编译产出的linker.cmd

1
2
3
4
5
6
7
8
 OUTPUT_ARCH("riscv")
OUTPUT_FORMAT("elf32-littleriscv")
MEMORY
{
ROM (rx) : ORIGIN = 541065216, LENGTH = 12582912
RAM (rwx) : ORIGIN = 0x80000000, LENGTH = ((16) << 10)
IDT_LIST (wx) : ORIGIN = 0xFFFFF7FF, LENGTH = 2K
}

基本可以知道

  • ROM地址在0x20400000,大小12M
  • RAM地址在0x80000000, 大小为16K
    当然也可以通过qemu的源代码sifive_e.c去看更多细节,也可以通过Zephyr内riscv32-fe310.dtsi来了解细节。

一个最短的risc-v程序

代码说明

前面说了我们要简单和纯净,编译后生成的代码段只有条实际指令,共12个字节,就是一直循环跳转__start,代码reset.s如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# __start被放入到.txt.entry段,位于.text的最开始
.section .text.entry
.globl __start
# 上电后CPU跳到ROM中__start开始运行:初始化$sp,栈是负增长,所以sp指向boot_stack_top,然后跳回到__start
__start:
la sp, boot_stack_top
j __start

# 要将stack放到bss后面,这里声明字段 .bss.stack 作为启动时的栈
.section .bss.stack
.global boot_stack
boot_stack:
# 4K 启动栈大小
.space 4096 * 4
.global boot_stack_top
boot_stack_top:
# 栈结尾

实际代码可以在https://github.com/lgl88911/riscv下载,之后会另外写文章分析

编译和运行

1. 环境设置

下载代码后,修改riscv/code/env.sh,将toolchain和qemu的路径修改为你安装的地方

1
export PATH=$PATH:/mnt/e/westz/zephyr-sdk-0.11.4/riscv64-zephyr-elf/bin/:/mnt/e/westz/zephyr-sdk-0.11.4/sysroots/x86_64-pokysdk-linux/usr/bin/

然后执行. ./env.sh,设置环境变量,编译和debug的时候才能找到工具链和qemu

2. 代码编译

执行下面命令进行代码编译

1
2
3
4
5
cd risc/code/
mkdir build
cd build
cmake ..
make

编译完后可以看到产生的init.bin只有12个字节。

3. 运行调试

我已经在Cmake将运行qemu和gdb集成起来了,执行后会直接跳到qemu上电的第一句停住,并用gdb连进去,细节之后的文章另行说明。执行make debug,开始debug,会看到进入gdb,并提示如下内容

1
2
3
4
5
6
Type "apropos word" to search for commands related to "word"...
Reading symbols from init.elf...
(No debugging symbols found in init.elf)
Remote debugging using :1234
0x00001000 in ?? ()
(gdb)

可以看到qemu上电后是从0x1000开始执行,我们在gdb中执行x /4i 0x1000看一下对应的指令

1
2
3
4
5
6
(gdb) x /4i 0x1000
=> 0x1000: lui t0,0x20400
0x1004: jr t0
0x1008: unimp
0x100a: unimp
(gdb)

其中lui是将t0的高位设置位0x20400低位清0,也就是让t0=0x20400000,然后用jr跳到0x20400000执行,从前面知道我们的代码就放在rom的0x20400000,我们在gdb中再看一下

1
2
3
4
5
6
(gdb) x /4i 0x20400000
0x20400000 <__start>: auipc sp,0x5fc04
0x20400004 <__start+4>: mv sp,sp
0x20400008 <__start+8>: j 0x20400000 <__start>
0x2040000c: unimp
(gdb)

可以明确的看到ROM内只有3条有效指令,先初始化sp,然后又跳回__start执行,循环往复。你可以通过在gdb中执行si来进行单步跟踪。

到此我们一个最简单的risc-v实验环境已经搭建完成,你已经有一个很简单的reset.s,在里面加入想要实验的risc-v汇编就可以单步执行观察效果了。