1 概述
- 实现全部目标指令集 (RV32I, Zicsr):使用从 riscv-opcodes 生成的文件为模板,编写代码。从 riscv-opcodes 生成的文件放在 resources/ 中。
- 实现 load ELF 文件的功能.
- 成功运行一段程序.
2 使用 riscv-opcodes 生成文件
2.1 使用 riscv-opcodes 生成各个指令集的 const 文件
-
make_for_rrv_iss.sh 是在 riscv-opcodes 中执行的脚本
#!/bin/bash
#
# make_for_rrv_iss.sh
#
# G -- IMAFDZicsr Zifencei
# c
#
# Define extensions to be built
# extensions=("rv_i" "rv32_i" "rv64_i")
extensions=("rv_i" "rv32_i" "rv64_i"
"rv_m" "rv32_m" "rv64_m"
"rv_a" "rv32_a" "rv64_a"
"rv_f" "rv32_f" "rv64_f"
"rv_d" "rv32_d" "rv64_d"
"rv_zicsr" "rv32_zicsr" "rv64_zicsr"
"rv_zifencei" "rv32_zifencei" "rv64_zifencei"
"rv_c" "rv32_c" "rv64_c"
"rv_system")
# Clean up before building
make clean
rm -f inst*.rs
# Function to process specific patterns
process_pattern() {
local pattern=$1
local output_file=$2
# Extract lines starting with the given pattern from inst_$ext.rs and append them to the output file
grep -h "^$pattern" "inst_$ext.rs" >>"$output_file"
# Use sed to delete lines starting with the given pattern in place
sed -i "/^$pattern/d" "inst_$ext.rs"
# echo "Lines starting with '$pattern' have been removed from inst_$ext.rs"
}
# Process each extension
for ext in "${extensions[@]}"; do
# Build with current extension
make EXTENSIONS="$ext" inst.rs || {
echo "Error: make failed for extension $ext"
exit 1
}
mv inst.rs "inst_$ext.rs" || {
echo "Error: Failed to rename file for extension $ext"
exit 1
}
process_pattern "const CSR_" "inst_${ext}_csr_reg.rs"
process_pattern "const CAUSE_" "inst_${ext}_cause.rs"
done
# Confirm completion
echo ">>>>>>>>> All extensions processed successfully."
# Function to remove duplicate files and rename the unique one
remove_duplicates_and_rename() {
local pattern=$1
local target_name=$2
local files=($(ls $pattern 2>/dev/null))
if [ ${#files[@]} -eq 0 ]; then
echo "No files found matching pattern: $pattern"
return
fi
local base_file="${files[0]}"
local keep_file="$base_file"
for file in "${files[@]:1}"; do
if cmp -s "$base_file" "$file"; then
# Remove duplicate file
rm "$file"
else
# Update base_file to compare with the next files
base_file="$file"
fi
done
# Rename the kept file
if [ "$keep_file" != "$target_name" ]; then
mv "$keep_file" "$target_name"
fi
}
# Call the function with patterns and target names
remove_duplicates_and_rename "inst_*_csr_reg.rs" "inst_csr_reg.rs"
remove_duplicates_and_rename "inst_*_cause.rs" "inst_cause.rs"
# Iterate over all .rs files in the current directory
for file in inst*.rs; do
# Use the grep command to check if the file contains the 'const ' keyword
# -q option makes grep run quietly, without outputting any text
# -F treats the pattern as a fixed string, not a regular expression
if ! grep -qF 'const ' "$file"; then
# If 'const ' is not found in the file, delete the file
rm "$file"
echo "Deleted empty file: $file"
else
# Create a temporary file
temp_file=$(mktemp)
# Use awk to replace multiple consecutive blank lines with a single blank line
awk 'BEGIN {RS=""; FS="\n"} {gsub(/\n+/,"\n"); print}' "$file" >"$temp_file"
mv "$temp_file" "$file"
fi
done
>>>>>>>>> All extensions processed successfully.
Deleted empty file: inst_rv32_a.rs
Deleted empty file: inst_rv32_d.rs
Deleted empty file: inst_rv32_f.rs
Deleted empty file: inst_rv32_m.rs
Deleted empty file: inst_rv32_zicsr.rs
Deleted empty file: inst_rv32_zifencei.rs
Deleted empty file: inst_rv64_zicsr.rs
Deleted empty file: inst_rv64_zifencei.rs
2.2 生成执行指令的桩函数
#!/bin/bash
#
# stub_for_rrv_iss.sh
#
# Ensure the execute directory exists
mkdir -p execute
# Traverse all files matching the pattern inst*.rs in the current directory
for file in inst_*.rs; do
# Extract the base name of the file without the 'inst' prefix (e.g., inst1.rs -> 1.rs)
base_name=$(basename "$file" .rs | sed 's/^inst_//')
# Use grep to find all unique MATCH_* patterns in the file
matches=$(grep -oP 'MATCH_\w+' "$file" | sort -u)
# Initialize the output file content
output_content=""
for match in $matches; do
# Convert MATCH_* to lowercase and replace underscores with spaces
func_name=$(echo "$match" | sed -e 's/MATCH_//' -e 's/_/ /g' -e 's/.*/\L&/')
# Convert spaces to underscores for the function name
func_name=$(echo "$func_name" | sed -e 's/ /_/g')
# Create the corresponding Rust code
rust_code=$(printf '\npub(crate) fn execute_%s(\n raw: MachineInstruction,\n core: &mut Core,\n _bus: &mut Bus,\n disasm: bool,\n) -> Result<Option<ExecutionReturnData>, RvCoreError> {\n todo!("Not implemented")\n}\n' "$func_name")
# Append the Rust code to the output content
output_content+="$rust_code"
done
# Check if output_content contains non-empty characters before saving
if [ -n "$output_content" ]; then
# Save the accumulated Rust code to the corresponding file in the execute directory
echo -e "$output_content" > "execute/${base_name}.rs"
else
echo "Warning: output_content is empty, skipping save for ${base_name}.rs"
fi
done
echo "All MATCH_* patterns have been processed and corresponding Rust files created in the execute directory."
3 实现生成文件中的桩函数
- 给
inst_rv_i.rs, inst_rv_system.rs, inst_rv_zicsr.rs, inst_rv32_i.rs 文件中的每个 const 常量添加 pub(crate).
- 实现
rv_i.rs, rv_system.rs, rv_zicsr.rs, rv32_i.rs 中未实现的函数.
- 优化代码: 消除重复, 使用
inst_cause.rs, inst_csr_reg.rs.
4 实现 load ELF 文件的功能
- 借助 goblin 来解析 ELF 文件.
- 使用下面 C 程序,生成的 ELF 文件,来测试 load ELF 文件的功能:
static int cnt1;
static int cnt2 = 0x10;
int main(void) {
return 0;
}
5 运行 hello world
5.1 riscv64-unknown-elf-gcc 生成的程序,是怎样退出的?
- 结合 ELF 的反汇编代码, newlib,spike,pk源代碼,查看spike的退出過程,相关源码摘录见下面的代码片段.
- 总结: 调用 exit(), exit() 通过 ecall(系统调用号是 93) 调用 pk 中实现的系统调用; pk 中的退出代码会对 tohost 和 fromhost 做一些处理,然后,进入死循环.
- 在 RRV-ISS 中, 将退出简化为: ISS 拦截 target 中, 编号为 93 的 ecall, 从而退出模拟器.
// #define SYS_exit 93
// newlib 中的調用鏈
void exit (int code)
void _exit(int exit_status)
#define syscall_errno(N, ARGC, A0, A1, A2, A3, A4, A5)
__internal_syscall
ecall // ecall NO is SYS_exit
// pk
void handle_trap(trapframe_t* tf)
static void handle_syscall(trapframe_t* tf)
long do_syscall(long a0, long a1, long a2, long a3, long a4, long a5, unsigned long n)
void sys_exit(int code)
void shutdown(int code)
void shutdown(int code)
{
frontend_syscall(SYS_exit, code, 0, 0, 0, 0, 0, 0);
while (1);
}
long frontend_syscall(long n, uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6)
htif_syscall()
void htif_syscall(uintptr_t arg)
{
do_tohost_fromhost(0, 0, arg);
}
volatile uint64_t tohost __attribute__((section(".htif")));
volatile uint64_t fromhost __attribute__((section(".htif")));
#define FROMHOST_DEV(fromhost_value) ((uint64_t)(fromhost_value) >> 56)
#define FROMHOST_CMD(fromhost_value) ((uint64_t)(fromhost_value) << 8 >> 56)
#define FROMHOST_DATA(fromhost_value) ((uint64_t)(fromhost_value) << 16 >> 16)
static void do_tohost_fromhost(uintptr_t dev, uintptr_t cmd, uintptr_t data)
{
spinlock_lock(&htif_lock);
__set_tohost(dev, cmd, data);
while (1) {
uint64_t fh = fromhost;
if (fh) {
if (FROMHOST_DEV(fh) == dev && FROMHOST_CMD(fh) == cmd) {
fromhost = 0;
break;
}
__check_fromhost();
}
}
spinlock_unlock(&htif_lock);
}
5.2 riscv64-unknown-elf-gcc 生成的程序,是怎样处理 printf 的?
5.3 运行 helloworld C 程序
- 放弃运行 helloworld C 程序, 原因如下:
- riscv64-unknown-elf-gcc 使用的启动代码包含 RISC-V 的 c 扩展指令
- riscv64-unknown-elf-gcc 使用的 printf 使用了多个系统调用
- 打算使用计算 MD5 的程序来验证,可以不用打印信息.