a. Prof Level Manual
本文档是专业阶段的实验手册。
专业阶段的实验围绕着 G233 虚拟开发板展开,你需要按照 G233 Board Datasheet 手册给出的硬件参数,完成实验任务,从而帮助你更好的掌握 QEMU 建模,理解 QEMU 模拟器的工作原理。
温馨提示
关于硬件建模部分,你可以尝试用 Rust 来实现(QEMU 有基本框架);若想进一步挑战自我,也可以尝试用 Rust 模拟客户机指令(需要自己从零实现)。
环境搭建
第一步,以 Ubuntu 22.04 为例,介绍如何安装 QEMU 开发环境。
# 备份 sources.list 文件
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak
# 启用 deb-src 源(将所有 deb 源对应的 deb-src 源解锁)
sudo sed -i '/^# deb-src /s/^# //' /etc/apt/sources.list
sudo apt-get update
sudo apt update && sudo apt build-dep qemu
# 创建工具链安装目录
sudo mkdir -p /opt/riscv
# 下载工具链压缩包
wget https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2025.09.28/riscv64-elf-ubuntu-22.04-gcc-nightly-2025.09.28-nightly.tar.xz -O riscv-toolchain.tar.xz
# 解压到安装目录
sudo tar -xJf riscv-toolchain.tar.xz -C /opt/riscv --strip-components=1
# 设置权限
sudo chown -R $USER:$USER /opt/riscv
echo "/opt/riscv/bin" >> $GITHUB_PATH
export PATH=$PATH:/opt/riscv/bin/
riscv64-unknown-elf-gcc --version # 验证编译器是否可用
# 安装 Rust 工具链
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
# 验证 Rust 安装
rustup --version
rustc --version
cargo --version
提示
安装 QEMU 开发环境,请参考导学阶段的 Step0: 搭建 QEMU 开发环境。
安装 RISC-V 的交叉编译工具链:下载地址,尽量选择最新的版本,要求安装 riscv64-unknown-elf- 类型。
安装 Rust,版本要求 >= 1.85,安装方法请参考 Rust 官方文档。
第二步,点击这里,自动 fork 作业仓库到 GTOC 组织下面,该仓库会为你开通代码上传权限。
第三步,需要 clone 刚刚 fork 好的仓库到本地:
git clone git@github.com:gevico/learning-qemu-2025-<你的 github 用户名>.git
# 比如 github 用户名是 zevorn,那么命令如下:
# git clone git@github.com:gevico/learning-qemu-2025-zevorn.git
第四步,添加上游远程仓库,用于同步上游的代码变更:
git remote add upstream git@github.com:gevico/gevico-classroom-learning-qemu-2025-learning-qemu.git
同步上游代码变更的常用命令:
git pull upstream main --rebase
最后一步,配置编译选项:
cd qemu
./configure --target-list=riscv64-softmmu \
--extra-cflags="-O0 -g3" \
--cross-prefix-riscv64=riscv64-unknown-elf- \
--enable-rust
执行时,如果看到以下输出,证明交叉编译工具链配置成功:
...
Cross compilers
riscv64 : riscv64-unknown-elf-gcc
...
提交代码
所有实验的测题源码,均放在仓库根目录路径: tests/gevico/tcg/riscv64/ 。
你需要熟读每个测题源码,理解每个测题的测试意图,并实现对应的 QEMU 建模功能(需要修改 QEMU 本体源码,非测题源码),文末会给出具体实验的介绍,辅助你阅读测题源码。
每次实验完成后,需要将你的代码提交到你的 fork 仓库中。
git add .
git commit -m "feat: subject..."
git push origin main
Note
请确保你的代码符合仓库的代码规范,包括代码格式、注释等。
测评验收
本地运行测题的方式:
make check-gevico-tcg
全部测题通过的情况下,你会看到如下输出:
BUILD riscv64-softmmu guest-tests
RUN riscv64-softmmu guest-tests
TEST 1/10 test-board-g233 on riscv64
TEST 2/10 test-insn-dma on riscv64
TEST 3/10 test-insn-sort on riscv64
TEST 4/10 test-insn-crush on riscv64
TEST 5/10 test-insn-expand on riscv64
...
如果你想运行某个测例,比如 test-board-g233,可以使用如下命令:
make -C build/tests/gevico/tcg/riscv64-softmmu/ run-board-g233
Note
当你使用 make -C 指定了路径以后,你可以通过输入 run- 和 tab 键来查看可以运行的测题
如果你想调试某个测例,比如 test-board-g233,可以使用如下命令启用 QEMU 的远程调试功能:
make -C build/tests/gevico/tcg/riscv64-softmmu gdbstub-board-g233
同理,你也可以通过 gdbstub- 和 tab 键来查看可以远程调试的测例。
然后需要你本地另起一个终端,使用 riscv-elf-gdb 加载被调试客户机二进制程序,进行远程调试。
Note
你需要熟读 G233 Board Datasheet 和测题的源码,来理解每个实验的测试意图,这会极大地方便你调试,提高开发效率。
每道测题 10 分,一共 10 道测题,共计 100 分,评分将显示到训练营的专业阶段排行榜。
实验介绍
为了方便设计测题,我们设计了一个虚拟板卡 G233,并且编写了 G233 Board Datasheet,用于描述 G233 板卡的硬件规格和功能。
该阶段涉及的所有实验的硬件参数,全部记录在 G233 Board Datasheet 中。熟读手册可以帮你更好的理解每个实验的测试意图。
所有实验的测题,均在 tests/gevico/tcg/riscv64/ 目录下,以 test- 开头的 .c 文件。
实验一 test-board-g233
源码路径: tests/gevico/tcg/riscv64/test-board-g233.c。
该实验用于验证 G233 Board 是否正常工作。你需要在 QEMU 中模拟 G233 Board。
基本代码已经存放在 hw/riscv/g233.c 中,需要你进一步补全它。
实验二 test-insn-dma
源码路径: tests/gevico/tcg/riscv64/test-insn-dma.c。
该实验用于验证 G233 Board 的 DMA 指令功能是否正常工作。你需要在 QEMU 中实现这条指令。
实验目标
本实验测试 G233 Board 的 DMA 指令,该指令用于矩阵转置操作:
- 将源矩阵按指定粒度进行转置
- 支持不同的粒度参数(0, 1, 2)
- 实现高效的矩阵数据重排
主要测试内容
1. 8x8 矩阵转置测试 (test_dma_grain_8x8)
- 测试矩阵:8x8 矩阵,粒度参数为 0
- 输入数据:按行优先顺序填充 0-63
- 验证转置结果的正确性
2. 16x16 矩阵转置测试 (test_dma_grain_16x16)
- 测试矩阵:16x16 矩阵,粒度参数为 1
- 输入数据:按行优先顺序填充 0-255
- 验证转置结果的正确性
3. 32x32 矩阵转置测试 (test_dma_grain_32x32)
- 测试矩阵:32x32 矩阵,粒度参数为 2
- 输入数据:按行优先顺序填充 0-1023
- 验证转置结果的正确性
4. 指令验证测试 (custom_dma)
- 使用内联汇编调用 DMA 指令
- 指令编码:
.insn r 0x7b, 6, 6, %0, %1, %2 - 参数:目标地址、源地址、粒度大小
5. 结果比较测试 (compare)
- 比较软件转置和硬件 DMA 指令的结果
- 验证 DMA 指令的正确性
这条指令的详细描述在 G233 Board Datasheet 中。
实验三 test-insn-sort
源码路径: tests/gevico/tcg/riscv64/test-insn-sort.c。
该实验用于验证 G233 Board 的 sort 指令功能是否正常工作。你需要在 QEMU 中实现这条指令。
实验目标
本实验测试 G233 Board 的 sort 指令,该指令用于数组排序操作:
- 对指定长度的数组进行升序排序
- 支持部分数组排序功能
- 实现高效的硬件排序算法
主要测试内容
1. 数组排序测试 (test_sort)
- 测试数组:32 个元素的整数数组
- 输入数据:
{3, 7, 23, 9, 81, 33, 4, 607747, 13, 2451, 323, 831, 0, ...} - 排序长度:前 16 个元素
- 验证排序结果的正确性
2. 软件排序实现 (bubble_sort)
- 使用冒泡排序算法作为参考实现
- 对数组进行升序排序
- 提供排序结果的对比基准
3. 指令验证测试 (custom_sort)
- 使用内联汇编调用 sort 指令
- 指令编码:
.insn r 0x7b, 6, 22, %0, %1, %2 - 参数:排序长度、数组地址、数组大小
4. 结果比较测试 (compare)
- 比较软件排序和硬件 sort 指令的结果
- 验证 sort 指令的正确性
这条指令的详细描述在 G233 Board Datasheet 中。
实验四 test-insn-crush
源码路径: tests/gevico/tcg/riscv64/test-insn-crush.c。
该实验用于验证 G233 Board 的 crush 指令功能是否正常工作。你需要在 QEMU 中实现这条指令。
实验目标
本实验测试 G233 Board 的 crush 指令,该指令用于数据压缩:
- 将 8 位数组元素的低 4 位提取出来
- 两两打包成一个 8 位数据
- 存储到目标数组中
主要测试内容
1. 数据压缩测试 (pack_low4bits)
- 输入数组:
{0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x1, 0x2, 0x3, 0x4} - 提取低 4 位并打包:
{0xBA, 0xDC, 0xFE, 0x21, 0x43}
2. 指令验证测试 (custom_crush)
- 使用内联汇编调用 crush 指令
- 指令编码:
.insn r 0x7b, 6, 38, %0, %1, %2 - 参数:目标地址、源地址、元素数量
3. 结果比较测试 (compare)
- 比较软件实现和硬件指令的结果
- 验证 crush 指令的正确性
这条指令的详细描述在 G233 Board Datasheet 中。
实验五 test-insn-expand
源码路径: tests/gevico/tcg/riscv64/test-insn-expand.c。
该实验用于验证 G233 Board 的 expand 指令功能是否正常工作。你需要在 QEMU 中实现这条指令。
实验目标
本实验测试 G233 Board 的 expand 指令,该指令用于数据扩展操作:
- 将 8 位数据的低 4 位和高 4 位分别提取
- 扩展为两个独立的 4 位数据
- 实现数据解压缩功能
主要测试内容
1. 数据扩展测试 (split_to_4bits)
- 输入数组:
{0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0xFA, 0x13, 0x24, 0x63, 0x74} - 扩展结果:每个 8 位数据拆分为两个 4 位数据
- 输出长度:原数组长度的 2 倍
2. 指令验证测试 (custom_expand)
- 使用内联汇编调用 expand 指令
- 指令编码:
.insn r 0x7b, 6, 54, %0, %1, %2 - 参数:目标地址、源地址、数据数量
3. 结果比较测试 (compare)
- 比较软件实现和硬件 expand 指令的结果
- 验证 expand 指令的正确性
这条指令的详细描述在 G233 Board Datasheet 中。
实验六 test-spi-jedec
源码路径: tests/gevico/tcg/riscv64/test-spi-jedec.c。
该实验用于验证 G233 Board 的 SPI-JEDEC 功能是否正常工作。你需要在 QEMU 中实现这个外设。
实验目标
本实验测试 G233 Board 的 SPI 外设基本功能:
- 验证 SPI 控制器初始化配置
- 测试 SPI 数据传输功能
- 读取 Flash 芯片的 JEDEC ID
- 验证 SPI 片选控制功能
主要测试内容
1. SPI 初始化测试 (spi_init)
- 配置 SPI 为主模式
- 设置波特率控制位
- 启用 SPI 功能
- 验证寄存器配置正确性
2. JEDEC ID 读取测试 (test_jedec_id)
- 发送 JEDEC ID 命令
0x9F - 读取 3 字节 JEDEC ID
- 验证 W25X16 Flash 返回
0xEF 0x30 0x15 - 测试片选控制时序
3. SPI 传输功能测试 (spi_transfer_byte)
- 测试单字节发送和接收
- 验证 TXE 和 RXNE 状态位
- 测试 SPI 忙状态检测
这个外设的详细描述在 G233 Board Datasheet 中。
实验七 test-flash-read
源码路径: tests/gevico/tcg/riscv64/test-flash-read.c。
该实验用于验证 G233 Board 的 flash-read 功能是否正常工作。你需要在 QEMU 中实现这个外设。
实验目标
本实验测试 G233 Board 的 SPI Flash 完整读写功能:
- 验证 Flash 状态寄存器读取
- 测试 Flash 扇区擦除功能
- 验证 Flash 页编程功能
- 测试 Flash 数据读取功能
主要测试内容
1. Flash 状态管理测试 (flash_read_status, flash_wait_busy)
- 读取 Flash 状态寄存器
- 检测 Flash 忙状态
- 等待操作完成
2. Flash 擦除测试 (flash_sector_erase)
- 发送扇区擦除命令
0x20 - 擦除 4KB 扇区
- 验证擦除操作完成
3. Flash 编程测试 (flash_page_program)
- 发送页编程命令
0x02 - 编程 256 字节数据
- 验证编程操作完成
4. Flash 读取测试 (flash_read_data)
- 发送读取命令
0x03 - 读取 256 字节数据
- 验证数据完整性
5. 完整读写测试 (flash_write_test_data)
- 生成测试数据(ASCII 字母循环)
- 执行完整的擦除 - 编程 - 读取流程
- 比较写入和读取的数据
- 验证所有 256 字节数据匹配
这个外设的详细描述在 G233 Board Datasheet 中。
实验八 test-flash-read-int
源码路径: tests/gevico/tcg/riscv64/test-flash-read-interrupt.c。
该实验用于验证 G233 Board 的中断功能是否正常工作。你需要在 QEMU 中实现这个外设的中断功能。
实验目标
本实验测试 G233 Board 的 SPI 中断驱动功能: - 验证 SPI 中断处理机制 - 测试中断驱动的数据传输 - 验证中断状态管理 - 测试完整的 Flash 中断读写功能
主要测试内容
1. SPI 中断处理测试 (spi0_interrupt_handler)
- 处理 TXE(发送缓冲区空)中断
- 处理 RXNE(接收缓冲区非空)中断
- 处理错误中断(UDR、OVR)
- 管理中断状态和计数
2. 中断驱动传输测试 (g233_spi_transfer_interrupt)
- 使用中断方式传输数据
- 管理发送和接收缓冲区
- 处理传输完成状态
- 支持超时检测
3. Flash 中断操作测试
- 中断驱动的状态读取
- 中断驱动的 JEDEC ID 读取
- 中断驱动的扇区擦除
- 中断驱动的页编程
- 中断驱动的数据读取
4. 完整中断测试 (flash_write_test_data)
- 使用中断方式执行完整 Flash 操作
- 验证中断驱动的数据完整性
- 测试中断计数和状态管理
- 比较中断方式和轮询方式的结果
这个中断对应的外设的详细描述在 G233 Board Datasheet 中。
实验九 test-spi-cs
源码路径: tests/gevico/tcg/riscv64/test-spi-cs.c。
该实验用于验证 G233 Board 的双 SPI Flash 片选功能是否正常工作。你需要在 QEMU 中实现 SPI 外设的片选控制功能。
实验目标
本实验测试 G233 Board 上连接的两个不同规格的 SPI Flash 芯片:
- Flash 0 (CS0): W25X16 (2MB)
- Flash 1 (CS1): W25X32 (4MB)
主要测试内容
1. Flash 识别测试 (test_flash_identification)
- 读取两个 Flash 芯片的 JEDEC ID
- 验证 Flash 0 返回
0xEF3015(W25X16) - 验证 Flash 1 返回
0xEF3016(W25X32)
2. 独立 Flash 操作测试 (test_individual_flash_operations)
- 分别对两个 Flash 进行擦除、编程、读取操作
- Flash 0 写入 A-Z 字母模式
- Flash 1 写入 a-z 字母模式
- 验证数据完整性
3. 交叉 Flash 操作测试 (test_cross_flash_operations)
在不同地址写入不同数据模式:
- Flash 0 @ 0x1000: 模式 A (0xAA)
- Flash 1 @ 0x1000: 模式 B (0x55)
- Flash 0 @ 0x2000: 模式 C (0x33)
- Flash 1 @ 0x2000: 模式 D (0xCC)
验证交叉操作不影响数据完整性
4. 交替操作测试 (test_alternating_operations)
- 在两个 Flash 之间进行交替读取操作
- 验证片选切换不会影响数据
5. 容量测试 (test_flash_capacity)
测试不同地址范围的数据操作:
- Flash 0: 测试 0x000000 和 0x1F0000 (2MB 范围)
- Flash 1: 测试 0x000000, 0x100000 和 0x3F0000 (4MB 范围)
6. 并发状态检查 (test_concurrent_status_check)
- 同时检查两个 Flash 的状态寄存器
- 验证片选控制不影响状态读取
这个外设的详细描述在 G233 Board Datasheet 中。
实验十 test-spi-overrun
源码路径: tests/gevico/tcg/riscv64/test-spi-overrun.c。
该实验用于验证 G233 Board 的 SPI 溢出错误检测功能是否正常工作。你需要在 QEMU 中实现 SPI 外设的错误检测和中断功能。
实验目标
本实验测试 SPI 外设在接收缓冲区未清空时继续接收数据时的溢出检测机制,包括:
- 中断模式下的溢出检测
- 轮询模式下的溢出检测
- 溢出标志的清除机制
主要测试内容
1. 中断模式溢出检测 (test_interrupt_overrun_detection)
启用 SPI 错误中断 (SPI_CR2_ERRIE)
-
发送第一个字节但不读取(保持 RXNE 标志)
-
发送第二个字节触发溢出
-
验证中断处理函数正确检测到溢出
2. 轮询模式溢出检测 (test_polling_overrun_detection)
-
禁用中断,使用轮询方式
-
重复上述溢出触发过程
-
通过状态寄存器轮询检测溢出标志
-
验证溢出标志的清除机制
溢出检测原理
SPI 溢出发生在以下情况:
- 接收缓冲区有数据未读取(RXNE = 1)
- 继续发送新数据
- 新数据到达但无法存储到已满的接收缓冲区
- 触发溢出错误标志(OVERRUN = 1)
这个外设的详细描述在 G233 Board Datasheet 中。