目标读者:向 machina 贡献代码的开发者。
- 行宽上限 80 列,所有代码和代码注释均遵守
.md文档文件不受 80 列限制- 缩进使用 4 个空格,禁止使用 Tab
- 续行对齐到上一行的参数起始位置,或缩进 4 个空格
// Good: 80 列以内,续行对齐
fn emit_modrm_offset(
buf: &mut CodeBuffer,
opc: u32,
r: Reg,
base: Reg,
offset: i32,
) {
// ...
}
// Good: 短函数签名可以单行
fn emit_ret(buf: &mut CodeBuffer) {
buf.emit_u8(0xC3);
}- 提交前必须运行
cargo fmt - 提交前必须通过
cargo clippy -- -D warnings - 使用
(-128..=127).contains(&x)替代x >= -128 && x <= 127 - 运算符优先级不明确时必须加括号:
(OPC + (x << 3)) | flag而非OPC + (x << 3) | flag
| 类型 | 风格 | 示例 |
|---|---|---|
| 类型/Trait | UpperCamelCase | ArithOp, CodeBuffer |
| 函数/方法 | snake_case | emit_arith_rr, low3 |
| 局部变量 | snake_case | rex, offset |
| 常量 | SCREAMING_SNAKE_CASE | P_REXW, STACK_ADDEND |
| 枚举变体 | UpperCamelCase | ArithOp::Add, Reg::Rax |
操作码常量使用 QEMU 原始命名风格以便交叉参考,通过
#![allow(non_upper_case_globals)] 抑制警告:
pub const OPC_ARITH_EvIb: u32 = 0x83;
pub const OPC_MOVL_GvEv: u32 = 0x8B;
pub const OPC_JCC_long: u32 = 0x80 | P_EXT;指令发射器遵循 emit_<指令>_<操作数模式> 模式:
emit_arith_rr -- 算术 reg, reg
emit_arith_ri -- 算术 reg, imm
emit_arith_mr -- 算术 [mem], reg
emit_arith_rm -- 算术 reg, [mem]
emit_mov_rr -- MOV reg, reg
emit_mov_ri -- MOV reg, imm
emit_load -- MOV reg, [mem]
emit_store -- MOV [mem], reg
emit_shift_ri -- 移位 reg, imm
emit_shift_cl -- 移位 reg, CL
- 注释使用英文编写
- 仅在逻辑不自明处添加注释,不注释显而易见的代码
- 公开 API 使用
///文档注释,简明扼要 - 内部实现使用
//行注释 - 代码注释同样遵守 80 列行宽(
.md文档文件不受此 限制)
/// Emit arithmetic reg, reg (ADD/SUB/AND/OR/XOR/CMP).
pub fn emit_arith_rr(
buf: &mut CodeBuffer,
op: ArithOp,
rexw: bool,
dst: Reg,
src: Reg,
) {
let opc =
(OPC_ARITH_GvEv + ((op as u32) << 3)) | rexw_flag(rexw);
emit_modrm(buf, opc, dst, src);
}- 枚举使用
#[repr(u8)]或#[repr(u16)]确保内存布局 - 枚举值显式赋值,不依赖自动递增
- 派生
Debug, Clone, Copy, PartialEq, Eq
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ArithOp {
Add = 0,
Or = 1,
Adc = 2,
Sbb = 3,
And = 4,
Sub = 5,
Xor = 6,
Cmp = 7,
}- 函数参数顺序:
buf在前,配置参数居中,操作数在后 rexw: bool参数控制 32/64 位操作- 立即数编码自动选择短形式(imm8 vs imm32)
- 函数体尽量短小,复杂逻辑拆分为子函数
// Good: buf 在前,rexw 居中,操作数在后
pub fn emit_load(
buf: &mut CodeBuffer,
rexw: bool,
dst: Reg,
base: Reg,
offset: i32,
) { ... }unsafe仅限以下场景:- JIT 代码缓冲区分配(mmap/mprotect)
- 调用生成的宿主代码(函数指针转换)
- 客户内存模拟的原始指针访问
- 后端内联汇编
- FFI 接口
- 每个
unsafe块必须有注释说明安全性保证 - 所有其他代码必须是安全的 Rust
- 测试位于独立的
machina-testscrate - 每个指令发射器至少一个测试验证字节编码
- 测试覆盖基础寄存器(Rax-Rdi)和扩展寄存器(R8-R15)
- 使用
emit_bytes辅助函数简化测试编写 - 测试函数名使用 snake_case,描述被测行为
fn emit_bytes(f: impl FnOnce(&mut CodeBuffer)) -> Vec<u8> {
let mut buf = CodeBuffer::new(4096).unwrap();
f(&mut buf);
buf.as_slice().to_vec()
}
#[test]
fn arith_add_rr_64() {
// add rax, rcx => 48 03 C1
let code = emit_bytes(|b| {
emit_arith_rr(b, ArithOp::Add, true, Reg::Rax, Reg::Rcx)
});
assert_eq!(code, [0x48, 0x03, 0xC1]);
}文档中的所有图表必须使用纯 ASCII 字符绘制,禁止使用 Unicode box-drawing 字符或其他非 ASCII 符号。
允许使用的字符:
| 字符 | 用途 |
|---|---|
+ |
边角连接点 |
- |
水平线 |
| ` | ` |
--> |
水平箭头(右) |
<-- |
水平箭头(左) |
v |
向下箭头 |
^ |
向上箭头 |
边框对齐规则:
- 所有矩形框的四角必须使用
+字符 - 水平边和垂直边必须严格对齐,不允许锯齿
- 箭头方向使用
-->或v,不使用 Unicode 箭头
+------------+ +------------+
| Frontend | --> | IR Builder |
+------------+ +------------+
|
v
+------------+
| Optimizer |
+------------+
|
v
+------------+
| Backend |
+------------+
禁止示例:
─,│,┌,┐,└,┘等 Unicode box-drawing 字符→,←,↑,↓等 Unicode 箭头├,┤,┬,┴,┼等 Unicode 连接符
- 每个 crate 的
lib.rs仅做模块声明和 re-export - 公开类型通过
pub use在 crate 根导出 - 相关功能放在同一文件,文件内按逻辑分节
- 使用
// -- Section name --分隔文件内的逻辑区域
- 并发路径优先使用"共享状态 + 每线程私有状态"拆分, 避免热路径共享锁。
- 新增并发字段时,必须在注释中写清:
- 谁持有写权限(例如
translate_lock) - 读路径是否 lock-free
- 可见性保证(Acquire/Release/Relaxed 的选择理由)
- 谁持有写权限(例如
- 性能优化提交必须附带可复现基准命令,至少包含:
machina-riscv64对dhrystoneqemu-riscv64同程序对照
- 涉及 TB 链路逻辑时必须补充:
- 并发正确性测试(
tests/src/exec/mttcg.rs) - 回归测试(至少一个 guest 测题)
- 并发正确性测试(
- 调试辅助输出统一走已有统计入口(
TCG_STATS=1), 避免在热路径直接打印日志。
Machina 项目的通用编码指南。Rust 特定规则见 Rust 指南,格式和命名约定见 代码风格。
禁止单字母命名或含糊缩写。名称应揭示意图。
// Bad
let v = t.read();
let p = addr >> 12;
// Good
let value = temp.read();
let page_number = addr >> PAGE_SHIFT;名称必须与代码的实际行为一致。如果名称是"count", 值就应该是计数——而不是索引、偏移量或掩码。
当值携带物理单位或量级时,将其嵌入名称。
let timeout_ms = 5000;
let frame_size_in_pages = 4;
let clock_freq_hz = 12_000_000;使用断言式命名:is_*、has_*、can_*、should_*。
let is_kernel_mode = mode == Prv::M;
let has_side_effects =
op.flags().contains(OpFlags::SIDE_EFFECT);重复代码的注释是噪音。解释非显而易见决策背后的原因。
// Bad: 重复代码
// Check if page is present
if pte.flags().contains(PteFlags::V) { ... }
// Good: 解释约束
// Sv39 spec: fetch falls through when V=0 in S-mode
// only traps in M-mode (spec 4.3.1)
if pte.flags().contains(PteFlags::V) { ... }当存在多种方案时,记录选择当前方案的原因。未来的 读者需要理解权衡,而不仅仅是结果。
实现硬件行为时,引用规范章节。
// RISC-V Privileged Spec 4.3.1 -- PTE attribute for
// global mappings
const PTE_G: u64 = 0x10;当文件过长或混合了不相关的职责时,拆分文件。名为
mmu.rs 的文件不应包含中断处理逻辑。
高层入口点放在前面。辅助函数和内部细节放在后面。 读者应能通过阅读第一个部分来理解公共 API。
在函数内部,将相关语句分组在一起。用空行分隔不同 的组。每组应表达算法中的一个步骤。
默认使用最窄的可见性。只暴露调用者需要的内容。
// Prefer
pub(crate) fn translate_one(ctx: &mut Context) { ... }
// Avoid
pub fn translate_one(ctx: &mut Context) { ... }在公共 API 边界(如 syscall 入口、设备 MMIO 写入) 验证输入。在 crate 内部,信任已验证的值。
如果值有约束,用类型系统编码,而不是在每个使用点 检查。
// Prefer: 非法状态不可表示
pub struct PhysicalPage(u64);
impl PhysicalPage {
pub fn new(frame: u64) -> Option<Self> {
(frame < MAX_PHYS_PAGE)
.then_some(PhysicalPage(frame))
}
}
// Avoid: 原始 u64 可以是任何值
fn map_page(frame: u64) { ... }一致地格式化错误消息。包含失败的操作、涉及的值或 标识符,以及(适用时)期望的范围。
"invalid PTE at {vpn}: reserved bits set"
"out of TB cache: capacity {cap}, requested {size}"
Machina 项目的 Rust 特定指南。通用编码规则见 编码指南,格式约定见 代码风格。
每个 unsafe 块都需要 // SAFETY: 注释解释操作
为何安全。每个 unsafe fn 或 unsafe trait 都需要
# Safety 文档段落,描述调用者必须满足的条件。
// SAFETY: buf points to a valid, RWX-mapped region of
// `len` bytes. The mmap call above guaranteed alignment
// and permissions.
unsafe {
core::ptr::copy_nonoverlapping(src.as_ptr(), buf, len)
}unsafe 仅允许用于 JIT 代码缓冲区管理、生成代码的
函数指针转换、TLB 快速路径中的原始指针访问、后端
发射器中的内联汇编和 FFI。所有其他代码必须是安全
Rust。
一个函数做一件事。如果需要注释来分隔段落,考虑 拆分。
目标最多 3 层嵌套。使用提前返回、let...else 和
? 来展平控制流。
// Prefer
let Some(pte) = page_table.walk(vpn) else {
return Err(MmuFault::InvalidPte);
};
// Avoid
if let Some(pte) = page_table.walk(vpn) {
// ... deeply nested logic ...
}使用枚举或拆分为两个函数。
// Prefer
pub fn emit_load_signed(
buf: &mut CodeBuffer, ...
) { ... }
pub fn emit_load_unsigned(
buf: &mut CodeBuffer, ...
) { ... }
// Avoid
pub fn emit_load(
buf: &mut CodeBuffer, signed: bool, ...
) { ... }当变体集合在编译期已知时,使用枚举。trait 对象
(dyn Trait)仅适用于开放式扩展。
// Prefer
enum Exception {
InstructionAccessFault,
LoadAccessFault,
StoreAccessFault,
// ...
}
// Avoid
trait Exception { fn handle(&self); }通过方法暴露字段而非直接 pub。这保留了后续添加
验证或日志的能力。
默认使用 pub(super) 或 pub(crate)。仅在外部
crate 确实需要访问时才使用 pub。
导入父模块,然后通过它调用函数。这使来源更明确。
// Prefer
use core::mem;
mem::replace(&mut slot, new_value)
// Avoid
use core::mem::replace;
replace(&mut slot, new_value)所有共享依赖版本必须在 workspace 根 Cargo.toml 的
[workspace.dependencies] 中声明。各 crate 使用
{ workspace = true } 引用。
在可能失败的地方不要 .unwrap() 或 .expect()。
使用 ? 将错误传播给调用者。
使用专用的错误枚举而非泛型 String 或
Box<dyn Error>。
#[derive(Debug)]
enum TranslateError {
InvalidOpcode(u32),
UnsupportedExtension(char),
BufferOverflow {
requested: usize,
available: usize,
},
}当存在多个锁时,文档化获取顺序并始终一致地遵守, 以防止死锁。
持有自旋锁时绝不执行 I/O 或阻塞操作。这包括内存 分配和打印语句。
Ordering 非常微妙。默认使用 SeqCst。仅在具有
文档化的性能原因且正确性论证已写入注释时,才放宽为
Acquire/Release/Relaxed。
翻译快速路径(TB 查找、代码执行、TLB 遍历)必须 避免 O(n) 操作。使用哈希表、直接映射缓存或索引数组。
通过引用传递大型结构体。当不需要所有权时使用
&[u8] 而非 Vec<u8>。避免在每次迭代中克隆
Arc。
优化提交必须包含显示改进的基准测试。
仅在函数无法完成工作时使用宏(例如重复声明、生成 match 分支)。
将 #[allow(...)] 或 #[expect(...)] 应用于特定项,
而非整个模块。
#[derive(Clone, Copy, Debug, PartialEq, Eq)]每个 workspace 成员必须包含:
[lints]
workspace = trueMachina 项目的 Git 提交和 Pull Request 约定。
module: subject
Body describing what changed and why.
Signed-off-by: Name <email>
- 格式:
module: subject - 祈使语气:
add、fix、remove——而非added、fixed、removed - 小写 subject,不加句号
- 总长度不超过 72 字符
| Module | 范围 |
|---|---|
core |
IR 类型、操作码、CPU trait |
accel |
IR 优化、寄存器分配、代码生成、执行引擎 |
guest/riscv |
RISC-V 前端(解码、翻译) |
decode |
.decode 解析器和代码生成 |
system |
CPU 管理器、GDB 桥接、WFI |
memory |
AddressSpace、MMIO、RAM 块 |
hw/core |
设备基础设施(qdev、IRQ、FDT) |
hw/intc |
PLIC、ACLINT |
hw/char |
UART |
hw/riscv |
参考机器、启动、SBI |
hw/virtio |
VirtIO MMIO 传输和设备 |
monitor |
QMP/HMP 控制台 |
gdbstub |
GDB 远程协议 |
difftest |
差分测试客户端 |
tests |
测试套件 |
docs |
文档 |
project |
跨模块变更(CI、Makefile、配置) |
| 动词 | 用途 |
|---|---|
Fix |
修复缺陷 |
Add |
引入新功能 |
Remove |
删除代码或功能 |
Refactor |
不改变行为的重构 |
Rename |
更改文件、模块或符号名称 |
Implement |
添加新的子系统或功能 |
Enable |
启用之前禁用的功能 |
Clean up |
无功能变更的小清理 |
Bump |
更新依赖版本 |
- 与 subject 之间空一行
- 描述修改了什么以及为什么——而非如何实现
- 每行不超过 80 字符
accel: fix register clobber in div/rem helpers
The x86-64 backend used RDX as a scratch register for
division without saving the guest's original value. Add
save/restore around the DIV instruction.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
guest/riscv: implement Zbs (single-bit operations)
Add bclr, bset, binv, bext for both register and immediate
forms. The decoder now recognizes the Zbs extension when
enabled in misa.
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
每个提交必须只做一个逻辑变更。不要在单个提交中混合 不相关的变更。如果你在提交消息中写了"以及",就应该 拆分。
每个提交之后,代码树必须处于可工作状态。禁止有破坏
性的中间状态。这确保 git bisect 始终有效。
在提交 PR 之前审查自己的分支时,将所有临时提交合并 到它们所属的提交中。临时提交包括:
- 修正早期提交中引入的拼写错误或缺陷的修复补丁
- 调整先前变更的补丁(如重命名、重新排序)
- 任何消息以
fixup!或squash!开头的提交
使用 git rebase -i 将这些临时提交折叠到原始提交
中。最终 PR 的历史应该是一组干净的逻辑变更序列,
而不是开发日记。
如果功能需要预备性的重构,将重构放在独立的提交中, 在功能提交之前。这使得每个提交更容易审查和二分定位。
本仓库的所有提交必须包含 Signed-off-by 行:
Signed-off-by: Chao Liu <chao.liu.zevorn@gmail.com>
禁止添加 AI 相关的签名行(如
Co-Authored-By: Claude)。
一个 PR 一个主题。混合了缺陷修复、重构和新功能的 PR 难以审查。
请求 review 前确保所有 CI 检查通过:
make test——所有测试通过make clippy——零警告make fmt-check——格式正确
当 PR 解决某个 issue 时,在描述中引用:
Closes #42
# 全量测试
cargo test
# 按 crate 运行
cargo test -p machina-core # 核心 IR 数据结构
cargo test -p machina-accel # 后端指令编码 + 执行循环
cargo test -p machina-tests # 主测试 crate
#(含全部分层测试)
# 按模块过滤
cargo test -p machina-tests core:: # 仅 core 模块
cargo test -p machina-tests backend:: # 仅 backend 模块
cargo test -p machina-tests decode:: # 仅 decode 模块
cargo test -p machina-tests frontend:: # 仅前端指令测试
cargo test -p machina-tests integration:: # 仅集成测试
cargo test -p machina-tests difftest # 仅差分测试
cargo test -p machina-tests hw_ # 仅硬件测试
# 运行单个测试
cargo test -- test_addi
cargo test -- test_c_li
# 查看详细输出
cargo test -- --nocapture
# 并行控制
cargo test -- --test-threads=1 # 串行(调试用)
cargo test -- --test-threads=4 # 4 线程迭代时优先用按主题命中的 filter,不要默认跑全量 make test。
cargo test 只接受一个 TESTNAME 位置参数,所以下表每行只给一
个 filter,按子串匹配选中所有符合的模块(例如 cli_ / riscv_
匹配所有同前缀模块)。若一个主题确实需要多个前缀,拆成多行,
每行都能原样复制粘贴到 cargo test -p machina-tests <filter>。
| 改动区域 | Filter | 备注 |
|---|---|---|
CLI 参数解析(-kernel、-bios、-drive、-netdev、-initrd、-m 等) |
cli_ |
子串匹配 tests/src/cli_* 全部模块;spawn 已编译二进制,首跑顺带重建。 |
| 反汇编器(RV64 / Zb*、RVC) | disas |
纯逻辑,跑得快。 |
Decode DSL(.decode 解析 + 代码生成) |
decode |
纯逻辑,跑得快。 |
| Memory region、FlatView、AddressSpace | memory_region |
纯逻辑,跑得快。 |
| ELF / 裸 kernel loader | hw_loader |
在内存里造 ELF。 |
| 硬件设备模型(PLIC、ACLINT、UART、GPIO、RTC、SD、SSI、IPI 等) | hw_ |
子串匹配 tests/src/hw_* 全部模块。 |
| RISC-V CPU 语义:CSR、MMU、PMP、异常 | riscv_ |
子串匹配 riscv_csr / riscv_mmu / riscv_pmp / riscv_exception / riscv_thead_csr / riscv_cpu_model。如需更窄,换成具体前缀(如 riscv_csr)。 |
| RISC-V frontend(指令解码 + IR 发射) | frontend |
覆盖 RV64I/M/F/C。 |
| x86-64 backend(指令编码) | backend |
x86-64 发射器;和 frontend 分两次跑。 |
| LoongArch 客户机(decode、exec、board) | loongarch_ |
子串匹配 loongarch_* 全部模块。 |
| VirtIO 传输 / block / net | virtio |
virtio_net 用例需要 Unix 宿主,CI 在 Linux 跑。 |
| Monitor(HMP、MMP/QMP、快照、TCP) | monitor |
TCP 用例 bind 127.0.0.1:0。 |
| GDB stub | gdbstub |
spawn 已编译二进制,注意首跑耗时。 |
| 启动工具(irbackend、irdump、冒烟) | tools |
spawn 已编译二进制。 |
| RISC-V difftest vs 用户态 QEMU | difftest |
子串匹配 frontend::difftest 下的 difftest_* 用例;PATH 上需要 qemu-riscv64(用户态),不是 qemu-system-riscv64。 |
| LoongArch difftest vs 用户态 QEMU | loongarch_difftest |
PATH 上需要 qemu-loongarch64(用户态)。 |
| Trace 工具 | trace |
纯逻辑,跑得快。 |
| Softfloat(IEEE 754) | softfloat |
纯逻辑,跑得快。 |
| 软 MMU | softmmu |
子串匹配 softmmu 和 softmmu_exec。 |
| 多 vCPU 并发回归 | exec::multi_vcpu |
MTTCG / 多 vCPU 的 exec 测试;跑得久,迭代时配 -- --nocapture。 |
源码卫生(禁 eprintln! 等) |
source_cleanliness |
提 PR 前的便宜检查。 |
| 硬件 oracle(对照参考) | oracle |
部分用例对照外部参考值;高并发下偶发抖动,必要时用 --test-threads=1 重跑。 |
测试数(顶层模块由 cargo test -p machina-tests -- --list 给出,
随 PR 合入会漂移)可这样实时统计:
cargo test -p machina-tests -- --list \
| grep ': test$' \
| sed 's/::.*//' \
| sort | uniq -c | sort -rn引用任何具体数字时请同时记下统计日期,避免把一次快照当成长期权威。
cargo clippy -- -D warnings # Lint 零警告
cargo fmt --check # 格式检查
cargo fmt # 自动格式化# 多 vCPU 并发回归
cargo test -p machina-tests exec::multi_vcpu -- --nocapture
# 打印执行统计(TB 命中率、链路 patch、hint 命中)
TCG_STATS=1 target/release/machina <machine-config>
# 简单性能对照(本机基线)
TIMEFORMAT=%R; time target/release/machina <machine-config>编码器辅助函数(tests/src/frontend/mod.rs):
| 格式编码器 | RVC 格式 |
|---|---|
rv_ci(f3, imm5, rd, imm4_0, op) |
CI 格式 |
rv_cr(f4, rd, rs2, op) |
CR 格式 |
rv_css(f3, imm, rs2, op) |
CSS 格式 |
rv_ciw(f3, imm, rdp, op) |
CIW 格式 |
rv_cl(f3, imm_hi, rs1p, imm_lo, rdp, op) |
CL 格式 |
rv_cs(f3, imm_hi, rs1p, imm_lo, rs2p, op) |
CS 格式 |
rv_cb(f3, off_hi, rs1p, off_lo, op) |
CB 格式 |
rv_cj(f3, target, op) |
CJ 格式 |
指令编码器:c_li, c_addi, c_lui, c_mv, c_add, c_sub, c_slli, c_addi4spn, c_addiw, c_j, c_beqz, c_bnez, c_ebreak。
| 测试 | 验证内容 |
|---|---|
test_c_li |
C.LI rd, imm --> rd = sext(imm) |
test_c_addi |
C.ADDI rd, nzimm --> rd += sext(nzimm) |
test_c_lui |
C.LUI rd, nzimm --> rd = sext(nzimm<<12) |
test_c_mv |
C.MV rd, rs2 --> rd = rs2 |
test_c_add |
C.ADD rd, rs2 --> rd += rs2 |
test_c_sub |
C.SUB rd', rs2' --> rd' -= rs2' |
test_c_slli |
C.SLLI rd, shamt --> rd <<= shamt |
test_c_addi4spn |
C.ADDI4SPN rd', nzuimm --> rd' = sp + nzuimm |
test_c_addiw |
C.ADDIW rd, imm --> rd = sext32(rd + imm) |
test_c_j |
C.J offset --> PC 跳转 |
test_c_beqz_* |
C.BEQZ taken / not-taken |
test_c_bnez_* |
C.BNEZ taken / not-taken |
test_c_ebreak |
C.EBREAK --> exit |
test_mixed_32_16 |
混合 32-bit + 16-bit 指令序列 |
cargo test -p machina-tests frontend:: # 全部前端测试
cargo test -p machina-tests test_c_ # 仅 RVC 测试
cargo test -p machina-tests test_mixed # 混合指令测试| 类别 | 指令 | 数量 |
|---|---|---|
| R-type ALU | add, sub, sll, srl, sra, slt, sltu, xor, or, and | 10 |
| I-type ALU | addi, slti, sltiu, xori, ori, andi, slli, srli, srai | 9 |
| LUI | lui | 1 |
| W-suffix R | addw, subw, sllw, srlw, sraw | 5 |
| W-suffix I | addiw, slliw, srliw, sraiw | 4 |
| Branch | beq, bne, blt, bge, bltu, bgeu | 6 |
未覆盖(待扩展):
- Load/Store(lb/lh/lw/ld/sb/sh/sw/sd)
- M 扩展(mul/div/rem 系列)
- auipc, jal, jalr(PC 相关,需特殊处理)
新增 R-type 指令(以 mulw 为例):
#[test]
fn difftest_mulw() {
let cases: Vec<(u64, u64)> = vec![
(V0, V0),
(V1, VNEG1),
(V32MAX, 2),
(VPATTERN, V32FF),
];
for (a, b) in cases {
difftest_alu(&rtype_test(
"mulw", "mulw", mulw(7, 5, 6), a, b,
));
}
}新增 I-type 指令(以 sltiu 为例):
#[test]
fn difftest_sltiu() {
let cases: Vec<(u64, i32)> = vec![
(V0, 0), (V0, 1), (VNEG1, -1),
];
for (a, imm) in cases {
difftest_alu(&itype_test(
"sltiu",
&format!("sltiu t2, t0, {imm}"),
sltiu(7, 5, imm), a,
));
}
}新增分支指令:
#[test]
fn difftest_beq() {
let cases = vec![
(V0, V0), (V0, V1), (VNEG1, VNEG1),
];
for (a, b) in cases {
difftest_branch(&BranchTest {
name: "beq", mnemonic: "beq",
insn_fn: beq, rs1_val: a, rs2_val: b,
});
}
}自定义模式(如 LUI 无源寄存器):
difftest_alu(&AluTest {
name: "lui",
asm: format!("lui t2, {upper}"),
insn: lui(7, imm),
init: vec![], // 无需初始化源寄存器
check_reg: 7,
});AluTest 字段:name(测试名)、asm(QEMU
汇编)、insn(机器码)、init(初始寄存器)、
check_reg(比较目标)。
# 运行全部 difftest
cargo test -p machina-tests difftest
# 运行单个 difftest
cargo test -p machina-tests difftest_add
# 并行运行
cargo test -p machina-tests difftest -- --test-threads=4
# 查看详细输出
cargo test -p machina-tests difftest -- --nocapture失败输出示例:
DIFFTEST FAIL [add]: x7 machina=0x64 qemu=0x65
含义:add 指令的 x7 寄存器,machina 计算结果为
0x64,QEMU 参考结果为 0x65,存在差异。
- x3(gp) 不可测试:QEMU 侧保留用于保存区基址
- PC 相关指令:auipc/jal/jalr 需计算相对偏移后 比较
- Load/Store:待 QemuLd/QemuSt 完善后扩展
- 随机化测试:可引入随机寄存器值生成器提高覆盖率
- 多指令序列:可扩展为多指令 difftest
设备测试直接构造设备实例,验证寄存器级行为:
#[test]
fn test_uart_tx_fifo() {
let mut uart = Ns16550::new();
// Write byte to THR, verify LSR shows TX empty
// after drain.
uart.write(UART_THR, b'A');
assert!(uart.read(UART_LSR) & LSR_THRE == 0);
let out = uart.drain_tx();
assert_eq!(out, vec![b'A']);
assert!(uart.read(UART_LSR) & LSR_THRE != 0);
}平台测试使用 RefMachine::new() 构造完整虚拟机,
验证设备接线、内存布局、FDT 生成和启动状态:
#[test]
fn test_ref_machine_init() {
let mut m = RefMachine::new();
m.init(&default_opts()).expect("init failed");
assert_eq!(m.cpu_count(), 1);
assert_eq!(m.ram_size(), 128 * 1024 * 1024);
}详见 tests/src/hw_ref_machine.rs(31 个平台测试)。
# 全部硬件测试
cargo test -p machina-tests hw_
# 仅参考机器测试
cargo test -p machina-tests hw_ref_machine::
# 仅 UART 测试
cargo test -p machina-tests hw_uart::
# 工具烟雾测试
cargo test -p machina-tests tools::在 tests/src/frontend/mod.rs 中添加:
#[test]
fn test_new_insn() {
let mut cpu = RiscvCpu::new();
// Set up initial register state.
cpu.gpr[1] = 100;
// Encode and run the instruction.
let insn = rv_i(42, 1, 0b000, 2, 0b0010011);
run_rv(&mut cpu, insn);
assert_eq!(cpu.gpr[2], 142);
}#[test]
fn test_c_new() {
let mut cpu = RiscvCpu::new();
cpu.gpr[10] = 5;
let insn = c_addi(10, 3); // C.ADDI x10, 3
run_rvc(&mut cpu, insn);
assert_eq!(cpu.gpr[10], 8);
}参见本文档「新增 Difftest 指南」一节。
- 在
tests/src/下新建hw_<device>.rs模块 - 在
tests/src/lib.rs中声明该模块 - 设备级测试直接实例化设备
- 平台级测试使用
RefMachine::new()和MachineOpts
设备测试模板:
#[test]
fn test_new_device_register() {
let mut dev = NewDevice::new();
dev.write(REG_OFFSET, expected_val);
assert_eq!(dev.read(REG_OFFSET), expected_val);
}平台测试模板:
#[test]
fn test_new_platform_feature() {
let mut m = RefMachine::new();
m.init(&default_opts()).expect("init failed");
let as_ = m.address_space();
// 验证设备 MMIO、FDT 节点、IRQ 接线等
}