支持RV32E的单周期NPC
📚 使用须知
- 本博客内容仅供学习参考
- 建议理解思路后独立实现
- 欢迎交流讨论
搭建面向riscv32e-npc的运行时环境
为NPC搭建基础设施
为NPC搭建sdb
为NPC添加trace支持
为NPC添加DiffTest支持
DiffTest是处理器调试的一大杀手锏, 在为NPC实现更多指令之前, 为其搭建DiffTest是一个明智的选择. 在这里, DUT是NPC, 而REF则选择NEMU. 为此, 你需要
- 在
nemu/src/cpu/difftest/ref.c中实现DiffTest的API, 包括difftest_memcpy(),difftest_regcpy()和difftest_exec(). 此外difftest_raise_intr()是为中断准备的, 目前暂不使用 - 在
NEMU的menuconfig中选择共享库作为编译的目标
Build target (Executable on Linux Native) --->
(X) Shared object (used as REF for differential testing)
- 重新编译NEMU, 成功后将会生成动态库文件
nemu/build/riscv32-nemu-interpreter-so - 在NPC的仿真环境中通过动态链接方式链接上述的动态库文件, 通过其中的API来实现DiffTest的功能, 具体可以参考NEMU的相关代码
尝试在打开DiffTest机制的情况下在NPC中正确运行dummy程序. 为了检查DiffTest机制是否生效, 你可以为NPC中addi指令的实现注入一个错误, 观察DiffTest是否能够按照预期地报告这一错误.
注意, 为了再次将NEMU编译成ELF, 你还需要修改NEMU中menuconfig的编译目标.
include/sdb.h
// include/sdb.h
#ifndef __SDB_H__
#define __SDB_H__
#include <stdbool.h>
#include <verilated_vcd_c.h>
enum SimState {
SIM_RUNNING,
SIM_PAUSED,
SIM_FINISHED
};
// SDB 初始化
void init_sdb(VerilatedVcdC* tfp);
void sdb_set_batch_mode();
// SDB 主循环
void sdb_mainloop();
// 单步执行控制
bool sdb_should_pause();
void sdb_step_tick();
bool sdb_is_enabled();
// 波形控制
void sdb_flush_waveform();
extern int check_watchpoints();
#endif
csrc/dpi_interface.c
// csrc/dpi_interface.c
#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <fstream>
#include <vector>
#include "verilated.h"
#include "verilated_dpi.h"
#include "../obj_dir/Vtop.h"
#include "../include/trace.h"
#ifdef __cplusplus
extern "C" {
#endif
// ==================== 全局调试变量 ====================
static uint32_t *cpu_gpr = NULL; // 寄存器文件指针
static uint32_t *cpu_csr = NULL; // CSR寄存器指针
static Vtop *top_ptr = NULL; // Verilog顶层模块指针
static bool sdb_enabled = false; // SDB使能标志
static bool ebreak_triggered = false;
static int ebreak_exit_code = 0;
static uint32_t last_pc = 0;
static uint32_t last_inst = 0;
// 修改这里:去掉 static,使变量全局可见
bool single_step_mode = false;
// ==================== 寄存器访问 ====================
void set_gpr_ptr(const svOpenArrayHandle r) {
cpu_gpr = (uint32_t *)(((VerilatedDpiOpenVar*)r)->datap());
printf("[DPI] GPR pointer set: %p\n", cpu_gpr);
}
void set_top_ptr(void* ptr) {
top_ptr = (Vtop*)ptr;
printf("[DPI] Top pointer set: %p\n", top_ptr);
}
uint32_t debug_read_gpr(int index) {
if (cpu_gpr && index >= 0 && index < 32) {
return cpu_gpr[index];
}
return 0xDEADBEEF;
}
uint32_t debug_read_pc() {
if (top_ptr) {
return top_ptr->pc;
}
return 0;
}
// ==================== 内存访问 ====================
static std::vector<uint8_t> pmem(64 * 1024 * 1024, 0); // 64MB内存
static uint32_t mem_base = 0x80000000;
void load_binary(const char* filename, uint32_t base_addr) {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Error: Cannot open file " << filename << std::endl;
return;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
if (base_addr < mem_base) {
std::cerr << "Error: Base address below memory base" << std::endl;
return;
}
uint32_t offset = base_addr - mem_base;
if (offset + size > pmem.size()) {
std::cerr << "Error: Binary too large for memory" << std::endl;
return;
}
if (!file.read((char*)(pmem.data() + offset), size)) {
std::cerr << "Error: Failed to read file" << std::endl;
return;
}
printf("[DPI] Loaded %ld bytes to 0x%08x\n", size, base_addr);
file.close();
}
int pmem_read(int raddr) {
uint32_t addr = raddr;
if (addr < mem_base) {
return 0;
}
uint32_t offset = addr - mem_base;
if (offset + 4 > pmem.size()) {
return 0;
}
// 读取32位字(小端序)
uint32_t value = 0;
value |= pmem[offset + 0] << 0;
value |= pmem[offset + 1] << 8;
value |= pmem[offset + 2] << 16;
value |= pmem[offset + 3] << 24;
#ifdef CONFIG_MTRACE
mtrace(1, addr, value, 4); // type=1: Read, size=4 bytes
#endif
return value;
}
// 添加指令fetch trace
void trace_ifetch(uint32_t pc, uint32_t inst) {
last_pc = pc;
last_inst = inst;
#ifdef CONFIG_ITRACE
itrace(pc, inst);
#endif
#ifdef CONFIG_MTRACE
mtrace(0, pc, inst, 4); // type=0: Instruction Fetch
#endif
}
void trace_func_call(uint32_t pc, uint32_t target, uint32_t ret_addr) {
#ifdef CONFIG_FTRACE
//printf("[FTRACE DEBUG] Function call: pc=0x%08x -> target=0x%08x, return=0x%08x\n",
// pc, target, ret_addr);
ftrace(pc, target, ret_addr, 0);
#endif
}
void trace_func_ret(uint32_t pc, uint32_t target) {
#ifdef CONFIG_FTRACE
//printf("[FTRACE DEBUG] Function return: pc=0x%08x -> target=0x%08x\n",
// pc, target);
ftrace(pc, target, 0, 1);
#endif
}
// 添加指令执行trace(供Verilog调用)
void trace_exec(uint32_t pc, uint32_t inst) {
// 这里可以添加更多执行时的trace信息
}
void pmem_write(int waddr, int wdata, char wmask) {
uint32_t addr = waddr;
if (addr < mem_base) {
return;
}
uint32_t offset = addr - mem_base;
if (offset + 4 > pmem.size()) {
return;
}
// 根据写掩码写入字节
if (wmask & 0x1) pmem[offset + 0] = (wdata >> 0) & 0xFF;
if (wmask & 0x2) pmem[offset + 1] = (wdata >> 8) & 0xFF;
if (wmask & 0x4) pmem[offset + 2] = (wdata >> 16) & 0xFF;
if (wmask & 0x8) pmem[offset + 3] = (wdata >> 24) & 0xFF;
}
// ==================== 调试控制 ====================
void debug_set_single_step(int enable) {
single_step_mode = (enable != 0);
printf("[DPI] Single step mode: %s\n", single_step_mode ? "ON" : "OFF");
}
int debug_should_pause() {
return single_step_mode ? 1 : 0;
}
// ==================== ebreak处理 ====================
void ebreak_handler(int code) {
printf("\n" "========================================" "\n");
if (code == 0) {
printf("HIT GOOD TRAP\n");
printf("Program completed successfully!\n");
} else {
printf("HIT BAD TRAP\n");
printf("Exit code: %d\n", code);
printf("Program failed!\n");
}
printf("========================================" "\n");
// 如果SDB启用,不立即退出,进入调试器
if (sdb_enabled) {
printf("Press Enter to continue debugging...\n");
// 设置暂停标志,由SDB处理
} else {
Verilated::gotFinish(true);
}
}
// ==================== 波形控制 ====================
void flush_waveform() {
// 此函数将在SDB中调用以刷新波形
printf("[DPI] Waveform flushed\n");
}
#ifdef __cplusplus
}
#endif
include/difftest.h
/***************************************************************************************
* NPC DiffTest 接口定义 - 与 NEMU 兼容
***************************************************************************************/
#ifndef __DIFFTEST_H__
#define __DIFFTEST_H__
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// NEMU 兼容类型定义
typedef uint32_t word_t;
typedef uint32_t paddr_t;
typedef uint32_t vaddr_t;
// DiffTest 方向定义
#define DIFFTEST_TO_DUT 0
#define DIFFTEST_TO_REF 1
// CPU 状态结构体 (必须与 NEMU 完全一致)
typedef struct {
// 32 个通用寄存器
word_t gpr[32];
// 程序计数器
vaddr_t pc;
} CPU_state;
// NEMU 兼容函数声明
void difftest_memcpy(paddr_t addr, void *buf, size_t n, bool direction);
void difftest_regcpy(void *dut, bool direction);
void difftest_exec(uint64_t n);
void difftest_raise_intr(word_t NO);
void difftest_init(int port);
// NPC DiffTest 接口
void npc_init_difftest(const char* ref_so_file, long img_size);
void npc_difftest_step(uint32_t pc, uint32_t inst);
void npc_difftest_enable();
void npc_difftest_disable();
void npc_difftest_close();
void difftest_skip_ref();
void difftest_skip_dut(int nr_ref, int nr_dut);
// DPI-C 接口
void difftest_init_dpi(const char* ref_so_file, long img_size);
void difftest_check_dpi(uint32_t pc, uint32_t inst);
void difftest_enable_dpi();
void difftest_disable_dpi();
#ifdef __cplusplus
}
#endif
#endif // __DIFFTEST_H__
csrc/difftest.c
/***************************************************************************************
* NPC DiffTest 实现 - 与 NEMU 兼容
***************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <assert.h>
#include <stdint.h>
#include <stdbool.h>
#include "../include/difftest.h"
#include "../include/npc.h"
// 全局 DiffTest 状态
static bool diff_enabled = false;
static bool skip_ref = false;
static int skip_dut_nr_inst = 0;
static void *ref_handle = NULL;
// NEMU 函数指针
static void (*ref_difftest_memcpy)(paddr_t addr, void *buf, size_t n, bool direction) = NULL;
static void (*ref_difftest_regcpy)(void *dut, bool direction) = NULL;
static void (*ref_difftest_exec)(uint64_t n) = NULL;
static void (*ref_difftest_raise_intr)(word_t NO) = NULL;
static void (*ref_difftest_init)(int port) = NULL;
// NPC 状态
static CPU_state npc_cpu;
static uint64_t inst_count = 0;
// 外部函数声明
extern "C" {
// 寄存器访问
uint32_t debug_read_gpr(int index);
uint32_t debug_read_pc();
// 内存访问
int pmem_read(int raddr);
// 调试控制
void ebreak_handler(int code);
}
// ==================== 辅助函数 ====================
// 从 NPC 读取当前 CPU 状态
static void read_npc_state() {
// 读取通用寄存器
for (int i = 0; i < 32; i++) {
npc_cpu.gpr[i] = debug_read_gpr(i);
}
// 读取 PC
npc_cpu.pc = debug_read_pc();
}
// 检查寄存器是否匹配
static bool checkregs(CPU_state *ref, vaddr_t pc) {
bool match = true;
// 检查 PC
if (npc_cpu.pc != ref->pc) {
printf("DIFFTEST ERROR: PC mismatch at NPC inst #%lu\n", inst_count);
printf(" NPC: 0x%08x\n", npc_cpu.pc);
printf(" REF: 0x%08x\n", ref->pc);
match = false;
}
// 检查通用寄存器
for (int i = 0; i < 32; i++) {
if (npc_cpu.gpr[i] != ref->gpr[i]) {
// 跳过 x0 寄存器(总是为 0)
if (i == 0) continue;
printf("DIFFTEST ERROR: GPR x%02d mismatch at NPC inst #%lu\n", i, inst_count);
printf(" NPC: 0x%08x (%d)\n", npc_cpu.gpr[i], npc_cpu.gpr[i]);
printf(" REF: 0x%08x (%d)\n", ref->gpr[i], ref->gpr[i]);
match = false;
}
}
if (!match) {
printf("\nFull register state at error:\n");
printf("NPC PC: 0x%08x, REF PC: 0x%08x\n", npc_cpu.pc, ref->pc);
for (int i = 0; i < 32; i++) {
if (npc_cpu.gpr[i] != ref->gpr[i] && i != 0) {
printf("x%02d: NPC=0x%08x REF=0x%08x %s\n",
i, npc_cpu.gpr[i], ref->gpr[i],
(npc_cpu.gpr[i] == ref->gpr[i]) ? "" : " <-- MISMATCH");
}
}
// 触发错误处理
ebreak_handler(1);
}
return match;
}
// ==================== NEMU 风格的 DiffTest 接口 ====================
// 初始化 DiffTest(NEMU 风格)
void npc_init_difftest(const char* ref_so_file, long img_size) {
if (ref_so_file == NULL) {
printf("DIFFTEST INFO: No reference simulator specified, DiffTest disabled\n");
return;
}
// 尝试加载参考模拟器动态库
ref_handle = dlopen(ref_so_file, RTLD_LAZY);
if (ref_handle == NULL) {
printf("DIFFTEST WARNING: Failed to load reference simulator: %s\n", dlerror());
printf("DIFFTEST INFO: Continuing without DiffTest\n");
return;
}
// 获取函数指针
ref_difftest_memcpy = (void (*)(paddr_t, void*, size_t, bool))dlsym(ref_handle, "difftest_memcpy");
ref_difftest_regcpy = (void (*)(void*, bool))dlsym(ref_handle, "difftest_regcpy");
ref_difftest_exec = (void (*)(uint64_t))dlsym(ref_handle, "difftest_exec");
ref_difftest_raise_intr = (void (*)(word_t))dlsym(ref_handle, "difftest_raise_intr");
ref_difftest_init = (void (*)(int))dlsym(ref_handle, "difftest_init");
// 检查必需函数
if (ref_difftest_memcpy == NULL || ref_difftest_regcpy == NULL ||
ref_difftest_exec == NULL || ref_difftest_init == NULL) {
printf("DIFFTEST ERROR: Reference simulator missing required functions\n");
dlclose(ref_handle);
ref_handle = NULL;
return;
}
printf("DIFFTEST INFO: Reference simulator loaded: %s\n", ref_so_file);
// 初始化参考模拟器
ref_difftest_init(1234); // 使用默认端口
// 同步内存(从 NPC 复制到 REF)
// 假设程序从 0x80000000 开始,大小为 img_size
uint8_t *buf = (uint8_t*)malloc(img_size);
if (buf == NULL) {
printf("DIFFTEST ERROR: Failed to allocate memory buffer\n");
return;
}
// 从 NPC 内存中读取数据
for (long i = 0; i < img_size; i += 4) {
uint32_t addr = 0x80000000 + i;
uint32_t data = pmem_read(addr);
buf[i + 0] = (data >> 0) & 0xFF;
buf[i + 1] = (data >> 8) & 0xFF;
buf[i + 2] = (data >> 16) & 0xFF;
buf[i + 3] = (data >> 24) & 0xFF;
}
// 复制到参考模拟器
ref_difftest_memcpy(0x80000000, buf, img_size, DIFFTEST_TO_REF);
free(buf);
// 读取 NPC 初始状态并复制到参考模拟器
read_npc_state();
ref_difftest_regcpy(&npc_cpu, DIFFTEST_TO_REF);
// 启用 DiffTest
diff_enabled = true;
skip_ref = false;
skip_dut_nr_inst = 0;
inst_count = 0;
printf("DIFFTEST INFO: DiffTest initialized and enabled (img_size=%ld)\n", img_size);
}
// 跳过参考模拟器的检查
void difftest_skip_ref() {
skip_ref = true;
skip_dut_nr_inst = 0;
}
// 跳过 NPC 指令
void difftest_skip_dut(int nr_ref, int nr_dut) {
skip_dut_nr_inst += nr_dut;
while (nr_ref-- > 0) {
ref_difftest_exec(1);
}
}
// 主要的 DiffTest 步骤函数
void npc_difftest_step(uint32_t pc, uint32_t inst) {
if (!diff_enabled || ref_handle == NULL) {
return;
}
// 读取 NPC 当前状态
read_npc_state();
// 处理跳过指令的情况
if (skip_dut_nr_inst > 0) {
CPU_state ref_r;
ref_difftest_regcpy(&ref_r, DIFFTEST_TO_DUT);
// 计算下一条指令的 PC(简化处理)
uint32_t npc = pc + 4;
if (ref_r.pc == npc) {
skip_dut_nr_inst = 0;
// 检查寄存器匹配
checkregs(&ref_r, npc);
return;
}
skip_dut_nr_inst--;
if (skip_dut_nr_inst == 0) {
printf("DIFFTEST ERROR: Cannot catch up with ref.pc = 0x%08x at pc = 0x%08x\n",
ref_r.pc, pc);
ebreak_handler(1);
}
return;
}
// 处理跳过参考模拟器的情况
if (skip_ref) {
ref_difftest_regcpy(&npc_cpu, DIFFTEST_TO_REF);
skip_ref = false;
return;
}
// 正常执行流程
inst_count++;
// 让参考模拟器执行一条指令
ref_difftest_exec(1);
// 获取参考模拟器的状态
CPU_state ref_state;
memset(&ref_state, 0, sizeof(ref_state));
ref_difftest_regcpy(&ref_state, DIFFTEST_TO_DUT);
// 检查状态匹配
checkregs(&ref_state, pc);
// 定期输出进度
if (inst_count % 1000 == 0) {
printf("DIFFTEST INFO: %lu instructions compared, PC=0x%08x\n",
inst_count, pc);
}
}
// 启用/禁用 DiffTest
void npc_difftest_enable() {
if (ref_handle != NULL) {
diff_enabled = true;
printf("DIFFTEST INFO: Enabled\n");
}
}
void npc_difftest_disable() {
diff_enabled = false;
printf("DIFFTEST INFO: Disabled\n");
}
// 清理资源
void npc_difftest_close() {
if (ref_handle != NULL) {
dlclose(ref_handle);
ref_handle = NULL;
printf("DIFFTEST INFO: Reference simulator unloaded\n");
}
printf("DIFFTEST INFO: Total instructions compared: %lu\n", inst_count);
}
// ==================== DPI-C 接口 ====================
// DPI-C 初始化包装函数
void difftest_init_dpi(const char* ref_so_file, long img_size) {
npc_init_difftest(ref_so_file, img_size);
}
// DPI-C 检查包装函数
void difftest_check_dpi(uint32_t pc, uint32_t inst) {
npc_difftest_step(pc, inst);
}
// DPI-C 启用/禁用包装函数
void difftest_enable_dpi() {
npc_difftest_enable();
}
void difftest_disable_dpi() {
npc_difftest_disable();
}
vsrc/top.v
// vsrc/top.v
// ==================== 顶层模块定义 ====================
module top(
input clk,
input rst,
output reg [31:0] outdata,
output reg [31:0] pc
);
// 内部信号声明
reg [31:0] next_pc;
wire [31:0] instruction;
// 寄存器文件信号
/* verilator lint_off UNOPTFLAT */
wire [4:0] rs1_addr, rs2_addr, rd_addr;
/* verilator lint_on UNOPTFLAT */
wire [31:0] rs1_data, rs2_data;
wire [31:0] rf_wdata;
wire rf_wen;
// 立即数信号
wire [31:0] imm_i, imm_u, imm_j;
// ALU信号
wire [31:0] alu_a, alu_b, alu_result;
wire [3:0] alu_op;
// 控制信号
wire is_auipc, is_lui, is_jal, is_jalr;
wire is_addi, is_sw, is_ebreak;
wire pc_src;
wire [31:0] jump_target;
// 写回数据选择
wire [1:0] wb_sel;
// 退出码信号
reg [31:0] exit_code;
// IDU输出信号
wire trace_call_en;
wire trace_ret_en;
wire [31:0] trace_ret_addr;
// 避免重复记录的寄存器
reg [31:0] last_trace_pc;
reg last_trace_valid;
reg idu_ftrace_call;
reg idu_ftrace_ret;
reg [31:0] idu_ftrace_target;
reg [31:0] idu_ftrace_ret_addr;
// ==================== DPI-C函数导入 ====================
import "DPI-C" function int pmem_read(input int raddr);
import "DPI-C" function void ebreak_handler(input int code);
import "DPI-C" function void set_top_ptr(input chandle ptr);
import "DPI-C" function void trace_func_call(input int pc, input int target, input int ret_addr);
import "DPI-C" function void trace_func_ret(input int pc, input int target);
// DiffTest DPI-C函数
import "DPI-C" function void difftest_init_dpi(input string ref_so_file, input longint img_size);
import "DPI-C" function void difftest_check_dpi(input int pc, input int inst);
import "DPI-C" function void difftest_enable_dpi();
import "DPI-C" function void difftest_disable_dpi();
// ==================== DiffTest配置 ====================
parameter DIFFTEST_ENABLED = 1'b1;
parameter DIFFTEST_REF_SO = "build/riscv32-nemu-interpreter-so";
parameter IMAGE_SIZE = 64 * 1024; // 64KB
// ==================== DiffTest状态 ====================
reg diff_enabled;
reg [63:0] diff_inst_count;
// ==================== PC更新逻辑 ====================
always @(posedge clk or posedge rst) begin
if (rst) begin
pc <= 32'h8000_0000;
exit_code <= 32'b0;
diff_enabled <= DIFFTEST_ENABLED;
diff_inst_count <= 0;
end else begin
pc <= next_pc;
// 如果当前指令是ebreak,保存a0寄存器值作为退出码
if (is_ebreak) begin
exit_code <= rs1_data;
end
end
end
// PC+4计算
wire [31:0] pc_plus_4 = pc + 4;
// 下一PC值选择
always @(*) begin
if (pc_src) begin
next_pc = jump_target;
end else begin
next_pc = pc_plus_4;
end
end
always @(posedge clk) begin
if (!rst) begin
// 处理函数调用跟踪
if (idu_ftrace_call) begin
trace_func_call(pc, idu_ftrace_target, idu_ftrace_ret_addr);
end
// 处理函数返回跟踪
if (idu_ftrace_ret) begin
trace_func_ret(pc, idu_ftrace_target);
end
end
end
// ==================== 模块实例化 ====================
ysyx_25110281_ifu ifu(
.pc(pc),
.clk(clk),
.ins(instruction)
);
ysyx_25110281_idu idu(
.ins(instruction),
.pc(pc),
.rs1_data(rs1_data),
// 寄存器地址输出
.rs1_addr(rs1_addr),
.rs2_addr(rs2_addr),
.rd_addr(rd_addr),
// 立即数输出
.imm_i(imm_i),
.imm_u(imm_u),
.imm_j(imm_j),
// 控制信号输出
.is_auipc(is_auipc),
.is_lui(is_lui),
.is_jal(is_jal),
.is_jalr(is_jalr),
.is_addi(is_addi),
.is_sw(is_sw),
.is_ebreak(is_ebreak),
// ALU控制
.alu_op(alu_op),
// PC控制
.pc_src(pc_src),
.jump_target(jump_target),
// 写回控制
.wb_sel(wb_sel),
.rf_wen(rf_wen),
.ftrace_call(idu_ftrace_call),
.ftrace_ret(idu_ftrace_ret),
.ftrace_target(idu_ftrace_target),
.ftrace_ret_addr(idu_ftrace_ret_addr)
);
RegisterFile regfile(
.clk(clk),
.rst(rst),
.raddr1(rs1_addr),
.raddr2(rs2_addr),
.rdata1(rs1_data),
.rdata2(rs2_data),
.waddr(rd_addr),
.wdata(rf_wdata),
.wen(rf_wen)
);
ysyx_25110281_alu alu(
.a(alu_a),
.b(alu_b),
.op(alu_op),
.result(alu_result)
);
// ==================== 数据通路连接 ====================
// ALU输入A选择:auipc时选PC,其他选rs1
assign alu_a = is_auipc ? pc : rs1_data;
// ALU输入B选择:立即数
assign alu_b = imm_i;
// 写回数据选择
assign rf_wdata = (wb_sel == 2'b00) ? alu_result :
(wb_sel == 2'b01) ? pc_plus_4 :
(wb_sel == 2'b10) ? imm_u :
32'b0;
// ==================== DiffTest逻辑 ====================
always @(posedge clk) begin
if (rst) begin
diff_inst_count <= 0;
end else if (diff_enabled) begin
// 跳过无效指令
if (instruction === 32'bx) begin
return;
end
// 跳过ebreak指令
if (is_ebreak) begin
return;
end
// 执行DiffTest检查
difftest_check_dpi(pc, instruction);
diff_inst_count <= diff_inst_count + 1;
// 定期输出状态
if (diff_inst_count % 1000 == 0 && diff_inst_count != 0) begin
$display("[DIFFTEST] Processed %0d instructions", diff_inst_count);
end
end
end
// ==================== DiffTest初始化 ====================
initial begin
if (DIFFTEST_ENABLED) begin
// 等待复位完成
#1000;
$display("[DIFFTEST] Initializing with reference: %s", DIFFTEST_REF_SO);
difftest_init_dpi(DIFFTEST_REF_SO, IMAGE_SIZE);
// 跳过前几条指令的检查
#100;
$display("[DIFFTEST] Initialization complete");
end
end
// ==================== ebreak处理 ====================
always @(posedge clk) begin
if (is_ebreak && !rst) begin
$display("Time=%0t: Detected ebreak, exit code = %0d", $time, exit_code);
ebreak_handler(exit_code);
end
end
// ==================== 最终化 ====================
final begin
if (DIFFTEST_ENABLED) begin
$display("[DIFFTEST] Final statistics:");
$display("[DIFFTEST] Total instructions compared: %0d", diff_inst_count);
end
end
endmodule
csrc/main.c
/***************************************************************************************
* NPC 主程序 - 集成完整 SDB 和 DiffTest
***************************************************************************************/
#include "verilated.h"
#include "verilated_vcd_c.h"
#include "../obj_dir/Vtop.h"
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <getopt.h>
#include "../include/sdb.h"
#include "../include/npc.h"
#include "../include/trace.h"
#include "../include/difftest.h"
// 外部 C 函数
extern "C" {
// 寄存器访问
uint32_t debug_read_gpr(int index);
uint32_t debug_read_pc();
void set_gpr_ptr(uint32_t* ptr);
void set_top_ptr(void* ptr);
// 内存访问
int pmem_read(int raddr);
void pmem_write(int waddr, int wdata, char wmask);
void load_binary(const char* filename, uint32_t base_addr);
// 调试控制
void debug_set_single_step(int enable);
int debug_should_pause();
void ebreak_handler(int code);
// SDB 函数
void cpu_exec(uint64_t n);
void isa_reg_display();
// DiffTest 函数
void npc_init_difftest(const char* ref_so_file, long img_size);
void npc_difftest_enable();
void npc_difftest_disable();
void npc_difftest_close();
}
// 全局变量定义
VerilatedContext* contextp = NULL;
VerilatedVcdC* tfp = NULL;
static Vtop* top;
SimState sim_state = SIM_RUNNING;
static uint64_t step_count = 0;
// 在这里定义 npc_exit_flag,使其全局可见
int npc_exit_flag = 0;
// 声明外部变量和函数
extern "C" {
extern bool single_step_mode;
}
// 步进和波形记录
void step_and_dump_wave() {
top->clk = !top->clk;
top->eval();
contextp->timeInc(1);
if (tfp) {
tfp->dump(contextp->time());
}
}
// cpu_exec 实现(SDB 调用)
extern "C" void cpu_exec(uint64_t n) {
if (n == (uint64_t)-1) {
// 连续执行:设置 step_count = 0 表示无限执行
step_count = 0;
sim_state = SIM_RUNNING;
printf("Starting continuous execution...\n");
} else {
// 单步执行 N 条指令
step_count = n*2;
sim_state = SIM_RUNNING;
printf("Stepping %lu instructions...\n", n);
}
}
// 参数解析
struct SimConfig {
const char* binary_file = nullptr;
const char* diff_so_file = nullptr;
const char* log_file = nullptr;
uint32_t entry_addr = 0x80000000;
bool dump_wave = true;
bool batch_mode = false;
bool difftest_enabled = true;
int max_cycles = 1000000;
} config;
// 获取文件大小
long get_file_size(const char* filename) {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) {
return 0;
}
return file.tellg();
}
// NEMU风格的参数解析
static int parse_args(int argc, char *argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " [OPTION...] IMAGE [args]\n\n";
std::cerr << "Options:\n";
std::cerr << " -b,--batch run with batch mode\n";
std::cerr << " -l,--log=FILE output log to FILE\n";
std::cerr << " -d,--diff=REF_SO run DiffTest with reference REF_SO\n";
std::cerr << " -nowave disable waveform dumping\n";
std::cerr << " -maxcycles=N maximum simulation cycles\n";
std::cerr << " -nodifftest disable DiffTest\n";
std::cerr << " -help show this help message\n";
return -1;
}
// NEMU 风格的选项表
const struct option table[] = {
{"batch" , no_argument , NULL, 'b'},
{"log" , required_argument, NULL, 'l'},
{"diff" , required_argument, NULL, 'd'},
{"help" , no_argument , NULL, 'h'},
{0 , 0 , NULL, 0 },
};
int o;
while ((o = getopt_long(argc, argv, "-bhl:d:", table, NULL)) != -1) {
switch (o) {
case 'b':
config.batch_mode = true;
sdb_set_batch_mode();
break;
case 'l':
config.log_file = optarg;
break;
case 'd':
config.diff_so_file = optarg;
break;
case 1: // 非选项参数(通常是镜像文件名)
config.binary_file = optarg;
break;
default:
return -1;
}
}
// 检查其他参数
for (int i = optind; i < argc; i++) {
if (strcmp(argv[i], "-nowave") == 0) {
config.dump_wave = false;
} else if (strcmp(argv[i], "-maxcycles") == 0 && i + 1 < argc) {
config.max_cycles = atoi(argv[++i]);
} else if (strcmp(argv[i], "-nodifftest") == 0) {
config.difftest_enabled = false;
} else if (strcmp(argv[i], "-entry") == 0 && i + 1 < argc) {
config.entry_addr = strtoul(argv[++i], NULL, 0);
}
}
// 如果没有指定DiffTest参考模拟器,使用默认值
if (config.difftest_enabled && !config.diff_so_file) {
config.diff_so_file = "build/riscv32-nemu-interpreter-so";
}
return 0;
}
void print_welcome() {
printf("\n");
printf(" _ __ ____ ___ \n");
printf(" / | / / / __ \\ / _ \\\n");
printf(" / |/ / / /_/ // __/\n");
printf(" / /| / / ____/ \\___/ \n");
printf("/_/ |_/ /_/ \n");
printf("\n");
printf("Welcome to riscv32-NPC!\n");
printf("For help, type \"help\"\n");
printf("\n");
}
int main(int argc, char** argv) {
print_welcome();
// 解析参数(NEMU风格)
if (parse_args(argc, argv) < 0) {
return 1;
}
// 检查文件
if (!config.binary_file) {
std::cerr << "Error: No binary file specified\n";
return 1;
}
std::ifstream file(config.binary_file);
if (!file) {
std::cerr << "Error: Cannot open binary file: " << config.binary_file << std::endl;
return 1;
}
file.close();
// 获取二进制文件大小
long img_size = get_file_size(config.binary_file);
if (img_size <= 0) {
std::cerr << "Error: Invalid binary file size\n";
return 1;
}
// Trace配置
const char* itrace_file = config.log_file ? "itrace.log" : NULL;
const char* mtrace_file = config.log_file ? "mtrace.log" : NULL;
const char* ftrace_file = config.log_file ? "ftrace.log" : NULL;
// 初始化 Verilator
contextp = new VerilatedContext;
top = new Vtop{contextp};
set_top_ptr(top);
// 设置波形
if (config.dump_wave) {
contextp->traceEverOn(true);
tfp = new VerilatedVcdC;
top->trace(tfp, 99);
tfp->open("waveform.vcd");
}
// 初始化 SDB
init_sdb(tfp);
// 初始化Trace系统
init_trace(itrace_file, mtrace_file, ftrace_file);
// 加载二进制
printf("[NPC] Loading binary: %s (size: %ld bytes) to 0x%08x\n",
config.binary_file, img_size, config.entry_addr);
load_binary(config.binary_file, config.entry_addr);
// 初始化 DiffTest
if (config.difftest_enabled && config.diff_so_file) {
printf("[DIFFTEST] Initializing with reference: %s\n", config.diff_so_file);
npc_init_difftest(config.diff_so_file, img_size);
} else if (config.difftest_enabled) {
printf("[DIFFTEST] No reference simulator specified\n");
printf("[DIFFTEST] Continuing without DiffTest\n");
config.difftest_enabled = false;
} else {
printf("[DIFFTEST] Disabled\n");
}
// 复位序列
printf("[NPC] Starting reset sequence...\n");
top->rst = 1;
for (int i = 0; i < 5; i++) {
step_and_dump_wave();
}
top->rst = 0;
printf("[NPC] Reset completed\n");
std::cout << "========================================" << std::endl;
std::cout << "NPC Simulator with SDB and DiffTest Started" << std::endl;
std::cout << "Binary: " << config.binary_file << std::endl;
std::cout << "DiffTest: " << (config.difftest_enabled ? "ENABLED" : "DISABLED") << std::endl;
std::cout << "Type 'help' for SDB commands" << std::endl;
std::cout << "========================================" << std::endl;
// 初始化仿真状态 - 确保停在 SDB
sim_state = SIM_PAUSED; // 初始状态为暂停
step_count = 0;
// 修改仿真循环部分
int cycle_count = 0;
while (cycle_count < config.max_cycles && !Verilated::gotFinish() && !npc_exit_flag) {
if (sim_state == SIM_PAUSED) {
// 进入 SDB
if (tfp) tfp->flush();
sdb_mainloop();
// 如果 SDB 设置了退出标志,退出循环
if (npc_exit_flag) {
break;
}
}
else if (sim_state == SIM_RUNNING) {
if (step_count > 0) {
// 单步执行模式:执行指定数量的周期
step_and_dump_wave();
cycle_count++;
step_count--;
if (step_count == 0) {
sim_state = SIM_PAUSED; // 单步执行完成,暂停
printf("Step execution completed. Total cycles: %d\n", cycle_count);
}
}
else if (step_count == 0) {
// 连续执行模式
step_and_dump_wave();
cycle_count++;
// 检查是否应该暂停(监视点触发或 ebreak)
if (check_watchpoints()) {
sim_state = SIM_PAUSED;
printf("Watchpoint triggered. Pausing at cycle %d\n", cycle_count);
}
// 定期输出进度
if (cycle_count % 10000 == 0) {
printf("[NPC] Cycle: %d, PC: 0x%08x\n", cycle_count, debug_read_pc());
}
}
}
else {
// 其他状态,等待
usleep(10000);
}
}
// 关闭DiffTest
if (config.difftest_enabled) {
npc_difftest_close();
}
// 关闭trace系统
close_trace();
// 清理
if (tfp) {
tfp->close();
delete tfp;
}
delete top;
delete contextp;
std::cout << "========================================" << std::endl;
std::cout << "NPC simulation finished. Cycles: " << cycle_count << std::endl;
std::cout << "========================================" << std::endl;
return 0;
}
Makefile
# NPC Makefile - 完整修复版本
CFLAGS += -I./include -g -O2 -Wall
LDFLAGS += -lreadline -ldl
# 配置选项
CONFIG_ITRACE ?= y
CONFIG_MTRACE ?= y
CONFIG_FTRACE ?= y
CONFIG_DIFFTEST ?= y # 添加DiffTest配置
# 添加Capstone相关配置
ifneq ($(CONFIG_ITRACE),)
# Capstone配置
CAPSTONE_DIR = tools/capstone/repo
CAPSTONE_LIB = $(CAPSTONE_DIR)/libcapstone.so
CAPSTONE_INC = $(CAPSTONE_DIR)/include
# 确保Capstone库已构建
$(CAPSTONE_LIB):
@echo "Building Capstone library..."
$(MAKE) -C tools/capstone/repo
@echo "Capstone library built successfully"
CFLAGS += -I$(CAPSTONE_INC) -DCONFIG_ITRACE
# 使用绝对路径链接库
CAPSTONE_ABS_DIR = $(realpath $(CAPSTONE_DIR))
LDFLAGS += -L$(CAPSTONE_ABS_DIR) -Wl,-rpath,$(CAPSTONE_ABS_DIR) -lcapstone
endif
ifneq ($(CONFIG_MTRACE),)
CFLAGS += -DCONFIG_MTRACE
endif
ifneq ($(CONFIG_FTRACE),)
CFLAGS += -DCONFIG_FTRACE
endif
# 添加DiffTest配置
ifneq ($(CONFIG_DIFFTEST),)
CFLAGS += -DCONFIG_DIFFTEST
# 添加dlopen支持
LDFLAGS += -ldl
endif
# 源文件
VSRC = $(wildcard ./vsrc/*.v)
CSRC = $(wildcard ./csrc/*.c)
# 根据配置排除或包含trace源文件
ifeq ($(CONFIG_ITRACE)$(CONFIG_MTRACE)$(CONFIG_FTRACE),)
CSRC := $(filter-out ./csrc/trace.c, $(CSRC))
endif
# 添加difftest.c到源文件列表
ifneq ($(CONFIG_DIFFTEST),)
CSRC += ./csrc/difftest.c
endif
# 默认目标
all: build
# 检查NEMU是否存在
check-nemu:
@if [ ! -f "../nemu/build/riscv32-nemu-interpreter-so" ]; then \
echo "WARNING: NEMU reference simulator not found at ../nemu/build/riscv32-nemu-interpreter-so"; \
echo "DiffTest will be disabled at runtime"; \
echo ""; \
echo "To build NEMU:"; \
echo " cd ../nemu && make isa=riscv32 nemu-interpreter-so"; \
else \
echo "NEMU reference found: ../nemu/build/riscv32-nemu-interpreter-so"; \
fi
# 构建 NPC 仿真器
build: $(CAPSTONE_LIB) check-nemu
@echo "Building NPC simulator with trace and DiffTest support..."
@echo "Config: ITRACE=$(CONFIG_ITRACE) MTRACE=$(CONFIG_MTRACE) FTRACE=$(CONFIG_FTRACE) DIFFTEST=$(CONFIG_DIFFTEST)"
@verilator -Wno-fatal -Wno-WIDTH \
$(VSRC) $(CSRC) \
--top-module top \
--cc \
--trace \
--exe \
--build \
-CFLAGS "$(CFLAGS)" \
-LDFLAGS "$(LDFLAGS)" \
--Mdir ./obj_dir
@echo "Build completed: ./obj_dir/Vtop"
# 运行
run: build
@if [ -z "$(ARGS)" ]; then \
echo "========================================"; \
echo "Error: No binary file specified"; \
echo ""; \
echo "Usage: make run ARGS=\"<path/to/binary.bin> [options]\""; \
echo ""; \
echo "Example:"; \
echo " make run ARGS=\"test.bin\""; \
echo " make run ARGS=\"test.bin -batch\""; \
echo " make run ARGS=\"test.bin -d ../nemu/build/riscv32-nemu-interpreter-so\""; \
echo "========================================"; \
exit 1; \
fi
@echo "========================================"
@echo "Running NPC with: $(ARGS)"
@echo "========================================"
# 检查二进制文件
@BINARY=$$(echo $(ARGS) | cut -d' ' -f1); \
if [ ! -f "$$BINARY" ]; then \
echo "Error: File not found: $$BINARY"; \
exit 1; \
fi
# 运行仿真器
@./obj_dir/Vtop $(ARGS) || true
@echo "========================================"
@echo "NPC simulation finished"
@echo "========================================"
# 批处理模式
batch: build
@if [ -z "$(ARGS)" ]; then \
echo "Usage: make batch ARGS=<binary.bin>"; \
exit 1; \
fi
@./obj_dir/Vtop $(ARGS) -batch
# 查看波形
wave:
@if [ -f "waveform.vcd" ]; then \
gtkwave waveform.vcd & \
else \
echo "No waveform found. Run 'make run' first."; \
fi
# 清理
clean:
rm -rf obj_dir *.vcd *.log test.bin
# 创建简单测试程序
test.bin:
@echo "Creating test program using Python..."
@python3 -c "with open('test.bin', 'wb') as f: f.write(b'\x13\x01\x10\x00\x93\x02\xa0\x00\x73\x00\x10\x00')"
@echo "Created test.bin"
@echo "Content verification:"
@hexdump -C test.bin
# 创建dummy测试程序
test/dummy.bin:
@mkdir -p test
@echo "Creating RISC-V dummy test program..."
@cat > test/dummy.s << 'EOF'
.text
.global _start
_start:
# Simple test program
addi x1, x0, 1 # x1 = 1
addi x2, x1, 2 # x2 = 3
addi x3, x2, 3 # x3 = 6
# Test lui
lui x4, 0x12345 # x4 = 0x12345000
# Test auipc
auipc x5, 0x10000 # x5 = PC + 0x10000000
# Test jal (skip next instruction)
jal x6, target
addi x7, x0, 1 # This should be skipped
target:
addi x8, x0, 2 # x8 = 2
# Success exit
addi a0, x0, 0 # Exit code 0 (success)
ebreak
EOF
@riscv64-unknown-elf-as -march=rv32i -mabi=ilp32 -o test/dummy.o test/dummy.s
@riscv64-unknown-elf-ld -Ttext=0x80000000 -o test/dummy.elf test/dummy.o
@riscv64-unknown-elf-objcopy -O binary test/dummy.elf test/dummy.bin
@echo "Dummy program created: test/dummy.bin"
# 测试 SDB
test: test.bin build
@echo "========================================"
@echo "Testing SDB..."
@echo "Instructions:"
@echo " 1. addi x2, x0, 1 # x2 = 1"
@echo " 2. addi x5, x0, 10 # x5 = 10"
@echo " 3. ebreak # breakpoint"
@echo "========================================"
@./obj_dir/Vtop test.bin
# 测试DiffTest
test-diff: test/dummy.bin build
@echo "========================================"
@echo "Testing DiffTest with dummy program"
@echo "========================================"
@if [ -f "../nemu/build/riscv32-nemu-interpreter-so" ]; then \
./obj_dir/Vtop test/dummy.bin -d ../nemu/build/riscv32-nemu-interpreter-so || true; \
else \
echo "NEMU reference not found, running without DiffTest"; \
./obj_dir/Vtop test/dummy.bin -nodifftest || true; \
fi
.PHONY: all build run batch wave clean test test-diff check-nemu
新增文件:
include/difftest.h - DiffTest头文件,定义与NEMU兼容的接口
csrc/difftest.c - DiffTest核心实现,与NEMU完全兼容
setup_difftest_env.sh - 环境设置脚本
修改文件:
vsrc/top.v - 集成DiffTest DPI-C接口,添加DiffTest逻辑
csrc/main.c - 添加DiffTest支持,NEMU风格参数解析
Makefile - 添加DiffTest配置选项和构建规则
Makefile
Makefile:158: *** missing separator. Stop.
直接使用这个简单的测试程序。
Makefile
# NPC Makefile - 完整修复版本
CFLAGS += -I./include -g -O2 -Wall
LDFLAGS += -lreadline -ldl
# 配置选项
CONFIG_ITRACE ?= y
CONFIG_MTRACE ?= y
CONFIG_FTRACE ?= y
CONFIG_DIFFTEST ?= y # 添加DiffTest配置
# 添加Capstone相关配置
ifneq ($(CONFIG_ITRACE),)
# Capstone配置
CAPSTONE_DIR = tools/capstone/repo
CAPSTONE_LIB = $(CAPSTONE_DIR)/libcapstone.so
CAPSTONE_INC = $(CAPSTONE_DIR)/include
# 确保Capstone库已构建
$(CAPSTONE_LIB):
@echo "Building Capstone library..."
$(MAKE) -C tools/capstone/repo
@echo "Capstone library built successfully"
CFLAGS += -I$(CAPSTONE_INC) -DCONFIG_ITRACE
# 使用绝对路径链接库
CAPSTONE_ABS_DIR = $(realpath $(CAPSTONE_DIR))
LDFLAGS += -L$(CAPSTONE_ABS_DIR) -Wl,-rpath,$(CAPSTONE_ABS_DIR) -lcapstone
endif
ifneq ($(CONFIG_MTRACE),)
CFLAGS += -DCONFIG_MTRACE
endif
ifneq ($(CONFIG_FTRACE),)
CFLAGS += -DCONFIG_FTRACE
endif
# 添加DiffTest配置
ifneq ($(CONFIG_DIFFTEST),)
CFLAGS += -DCONFIG_DIFFTEST
# 添加dlopen支持
LDFLAGS += -ldl
endif
# 源文件
VSRC = $(wildcard ./vsrc/*.v)
CSRC = $(wildcard ./csrc/*.c)
# 根据配置排除或包含trace源文件
ifeq ($(CONFIG_ITRACE)$(CONFIG_MTRACE)$(CONFIG_FTRACE),)
CSRC := $(filter-out ./csrc/trace.c, $(CSRC))
endif
# 添加difftest.c到源文件列表
ifneq ($(CONFIG_DIFFTEST),)
CSRC += ./csrc/difftest.c
endif
# 默认目标
all: build
# 检查NEMU是否存在
check-nemu:
@if [ ! -f "../nemu/build/riscv32-nemu-interpreter-so" ]; then \
echo "WARNING: NEMU reference simulator not found at ../nemu/build/riscv32-nemu-interpreter-so"; \
echo "DiffTest will be disabled at runtime"; \
echo ""; \
echo "To build NEMU:"; \
echo " cd ../nemu && make isa=riscv32 nemu-interpreter-so"; \
else \
echo "NEMU reference found: ../nemu/build/riscv32-nemu-interpreter-so"; \
fi
# 构建 NPC 仿真器
build: $(CAPSTONE_LIB) check-nemu
@echo "Building NPC simulator with trace and DiffTest support..."
@echo "Config: ITRACE=$(CONFIG_ITRACE) MTRACE=$(CONFIG_MTRACE) FTRACE=$(CONFIG_FTRACE) DIFFTEST=$(CONFIG_DIFFTEST)"
@verilator -Wno-fatal -Wno-WIDTH \
--timing \
$(VSRC) $(CSRC) \
--top-module top \
--cc \
--trace \
--exe \
--build \
-CFLAGS "$(CFLAGS)" \
-LDFLAGS "$(LDFLAGS)" \
--Mdir ./obj_dir
@echo "Build completed: ./obj_dir/Vtop"
# 运行
run: build
@if [ -z "$(ARGS)" ]; then \
echo "========================================"; \
echo "Error: No binary file specified"; \
echo ""; \
echo "Usage: make run ARGS=\"<path/to/binary.bin> [options]\""; \
echo ""; \
echo "Example:"; \
echo " make run ARGS=\"test.bin\""; \
echo " make run ARGS=\"test.bin -batch\""; \
echo " make run ARGS=\"test.bin -d ../nemu/build/riscv32-nemu-interpreter-so\""; \
echo "========================================"; \
exit 1; \
fi
@echo "========================================"
@echo "Running NPC with: $(ARGS)"
@echo "========================================"
# 检查二进制文件
@BINARY=$$(echo $(ARGS) | cut -d' ' -f1); \
if [ ! -f "$$BINARY" ]; then \
echo "Error: File not found: $$BINARY"; \
exit 1; \
fi
# 运行仿真器
@./obj_dir/Vtop $(ARGS) || true
@echo "========================================"
@echo "NPC simulation finished"
@echo "========================================"
# 批处理模式
batch: build
@if [ -z "$(ARGS)" ]; then \
echo "Usage: make batch ARGS=<binary.bin>"; \
exit 1; \
fi
@./obj_dir/Vtop $(ARGS) -batch
# 查看波形
wave:
@if [ -f "waveform.vcd" ]; then \
gtkwave waveform.vcd & \
else \
echo "No waveform found. Run 'make run' first."; \
fi
# 清理
clean:
rm -rf obj_dir *.vcd *.log test.bin
# 创建简单测试程序
test.bin:
@echo "Creating test program using Python..."
@python3 -c "with open('test.bin', 'wb') as f: f.write(b'\x13\x01\x10\x00\x93\x02\xa0\x00\x73\x00\x10\x00')"
@echo "Created test.bin"
@echo "Content verification:"
@hexdump -C test.bin
# 测试 SDB
test: test.bin build
@echo "========================================"
@echo "Testing SDB..."
@echo "Instructions:"
@echo " 1. addi x1, x0, 1 # x1 = 1"
@echo " 2. addi x2, x0, 10 # x2 = 10"
@echo " 3. ebreak # breakpoint"
@echo "========================================"
@./obj_dir/Vtop test.bin
# 测试DiffTest - 使用简单的test.bin
test-diff: test.bin build
@echo "========================================"
@echo "Testing DiffTest with simple test program"
@echo "========================================"
@if [ -f "../nemu/build/riscv32-nemu-interpreter-so" ]; then \
./obj_dir/Vtop test.bin -d ../nemu/build/riscv32-nemu-interpreter-so || true; \
else \
echo "NEMU reference not found, running without DiffTest"; \
./obj_dir/Vtop test.bin -nodifftest || true; \
fi
.PHONY: all build run batch wave clean test test-diff check-nemu
路径加载
NEMU实际在../nemu/build/riscv32-nemu-interpreter-so但DiffTest尝试从 build/riscv32-nemu-interpreter-so 加载(这是NPC目录下的build目录)
DIFFTEST INFO: Reference simulator loaded: ../nemu/build/riscv32-nemu-interpreter-so
[src/memory/paddr.c:50 init_mem] physical memory area [0x80000000, 0x87ffffff]
Loading image to address 0x80000000
Image size: 20 bytes
DIFFTEST INFO: DiffTest initialized and enabled (img_size=12)
[NPC] Starting reset sequence...
[DIFFTEST] Initializing with reference: build/riscv32-nemu-interpreter-so
DIFFTEST WARNING: Failed to load reference simulator: build/riscv32-nemu-interpreter-so: cannot open shared object file: No such file or directory
DIFFTEST INFO: Continuing without DiffTest
[DIFFTEST] Initialization complete
主要修改内容:
在Makefile中添加了NEMU库的复制规则:确保NEMU库被复制到NPC的build目录
更新了路径变量:使用正确的相对路径
简化了DiffTest逻辑:移除了Verilog中的初始化,依赖C代码的初始化
添加了等待计数器:确保C代码初始化完成后才开始检查
问题分析
时机问题:DiffTest初始化是在复位前完成的,但Verilog侧的检查可能没有及时启动
指令执行太快:程序只有3条指令,可能在DiffTest准备好之前就执行完了
检查条件不满足:可能检查条件有问题
从输出可以看到:
在Time=6执行第一条指令
在Time=8执行第二条指令
在Time=10执行第三条指令(ebreak)
而DiffTest在Time=8才显示"Verilog side ready for DiffTest",此时已经错过了两条指令。
1. 修复 difftest.c - 改进NPC状态读取
2. 修复 difftest.c - 改进内存同步
3. 修复 top.v - 调整DiffTest检查时机
4. 修复 difftest.c - 改进错误处理
继续修改,关键改进点
时序同步:在Verilog中使用状态机控制DiffTest的激活时机
延迟检查:将指令检查延迟一个周期,确保指令已执行完成
详细调试:在每个关键步骤添加调试输出
更长的测试程序:创建包含多条指令的测试程序,确保有足够的时间进行DiffTest比较
排除无效指令:过滤掉ebreak和空指令
当前项目状态分析
✅ 基本功能正常:
CPU能够正确执行RISC-V指令
寄存器写入正常(x2=1, x5=10)
ebreak处理正常
Verilator仿真流程完整
⚠️ DiffTest问题:
Total instructions compared: 0 表示DiffTest没有进行任何指令比对
这可能是因为DiffTest初始化或同步时机有问题
DiffTest初始化成功 - 参考模拟器已加载
内存同步完成 - 12字节复制到参考模拟器
NPC执行了3条指令 - addi x2, x0, 1; addi x5, x0, 10; ebreak
但没有进行任何比对 - 指令比对计数为0
执行时机:确保NPC和参考模拟器从相同的状态开始执行相同的指令
状态同步:在NPC执行指令前,将状态同步到参考模拟器
内存同步:确保参考模拟器的内存与NPC一致
调试输出:添加详细的调试信息以便定位问题
修改总结:
-
top.v 主要修改:
移除了延迟寄存器逻辑(diff_delayed_pc, diff_delayed_inst, diff_delayed_valid)
简化了DiffTest检查逻辑,直接在指令执行后的下一个周期检查
添加了更多调试信息输出
修复了复位计数器逻辑
-
difftest.c 主要修改:
实现了正确的DiffTest执行流程:
读取NPC执行前的状态
同步状态到参考模拟器
参考模拟器执行一条指令
获取参考模拟器执行后的状态
NPC已经执行指令,读取执行后的状态
比较两者状态
添加了详细的调试信息
修复了内存复制逻辑
-
dpi_interface.c 主要修改:
添加了调试信息(可选启用)
完善了内存访问的错误处理
-
main.c 主要修改:
添加了DiffTest初始化的调试信息
确保在复位后等待足够时间让DiffTest初始化完成
主要问题解决:
Difftest初始化时机问题:将Difftest初始化移到复位完成之后,确保寄存器指针已设置
寄存器读取失败:在read_npc_state中添加调试信息,并在读取到无效值时进行修复
复位逻辑简化:移除复杂的复位计数器,在第一个非复位周期标记Difftest就绪
执行流程优化:添加更多调试信息,便于定位问题
Difftest初始化时机太晚:NPC已经执行了第一条指令(addi x2, x0, 1)才初始化Difftest
状态读取时机错误:在Difftest检查时读取的NPC状态是执行前的状态,而不是执行后的状态
PC不匹配:NPC的PC是0x80000004(执行前),而参考模拟器的PC是0x80000008(执行后)
修改方案:
-
修改 main.c - 调整初始化顺序
-
修改 difftest.c - 修复时序和状态读取
-
修改 top.v - 调整Difftest检查时机
主要修改:
-
main.c:将Difftest初始化移到复位之前,确保在第一条指令执行前就已初始化
-
difftest.c:
-
简化Difftest步骤:让参考模拟器先执行,然后读取NPC状态比较
-
设置正确的初始状态(PC=0x80000000)
-
改进调试信息输出
- top.v:将Difftest检查移到时钟下降沿,确保NPC状态已稳定
检查时机有点晚:第一条指令在PC=0x80000004时才检查PC=0x80000000的指令
PC地址显示有点混乱:检查时显示的PC是下一条指令的地址
这是正常的,因为:
NPC在执行指令的同一周期就更新了PC
我们是在时钟下降沿检查,此时PC已经更新到下一条指令
为了让日志更清晰,我们可以进一步优化。
主要优化:
top.v:
添加了last_check_pc和last_check_inst寄存器,记录要检查的指令
修改日志输出,清晰显示执行PC和指令,以及下一条PC
避免了检查无效指令(指令为0的情况)
difftest.c:
简化了调试输出,只显示关键信息
改进了匹配成功的输出信息,显示PC跳转情况
根据之前分析的代码结构,问题很可能出在立即数选择上。auipc指令应该使用imm_u(已经左移12位的立即数),而不是imm_i。
// 原代码:
assign alu_b = imm_i;
// 新代码:
assign alu_b = (is_auipc | is_lui) ? imm_u : imm_i;
完整代码
scrc/difftest.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <assert.h>
#include <stdint.h>
#include <stdbool.h>
#include "../include/difftest.h"
#include "../include/npc.h"
// 全局 DiffTest 状态
static bool diff_enabled = false;
static bool skip_ref = false;
static int skip_dut_nr_inst = 0;
static void *ref_handle = NULL;
// NEMU 函数指针
static void (*ref_difftest_memcpy)(paddr_t addr, void *buf, size_t n, bool direction) = NULL;
static void (*ref_difftest_regcpy)(void *dut, bool direction) = NULL;
static void (*ref_difftest_exec)(uint64_t n) = NULL;
static void (*ref_difftest_raise_intr)(word_t NO) = NULL;
static void (*ref_difftest_init)(int port) = NULL;
// NPC 状态
static CPU_state npc_cpu;
static uint64_t inst_count = 0;
static uint32_t last_pc = 0;
// 外部函数声明
extern "C" {
// 寄存器访问
uint32_t debug_read_gpr(int index);
uint32_t debug_read_pc();
// 内存访问
int pmem_read(int raddr);
// 调试控制
void ebreak_handler(int code);
}
// ==================== 辅助函数 ====================
// 从 NPC 读取当前 CPU 状态
static void read_npc_state() {
// 重置NPC状态结构
memset(&npc_cpu, 0, sizeof(npc_cpu));
// 读取通用寄存器
for (int i = 0; i < 32; i++) {
uint32_t val = debug_read_gpr(i);
npc_cpu.gpr[i] = val;
}
// 读取 PC - 确保读取的是正确的PC
uint32_t current_pc = debug_read_pc();
npc_cpu.pc = current_pc;
last_pc = current_pc;
}
// ==================== NEMU 风格的 DiffTest 接口 ====================
// 初始化 DiffTest(NEMU 风格)
void npc_init_difftest(const char* ref_so_file, long img_size) {
if (ref_so_file == NULL) {
printf("[DIFFTEST INFO] No reference simulator specified, DiffTest disabled\n");
return;
}
// 尝试加载参考模拟器动态库
printf("[DIFFTEST] Loading reference simulator: %s\n", ref_so_file);
ref_handle = dlopen(ref_so_file, RTLD_LAZY);
if (ref_handle == NULL) {
printf("[DIFFTEST WARNING] Failed to load reference simulator: %s\n", dlerror());
printf("[DIFFTEST INFO] Continuing without DiffTest\n");
return;
}
// 获取函数指针
ref_difftest_memcpy = (void (*)(paddr_t, void*, size_t, bool))dlsym(ref_handle, "difftest_memcpy");
ref_difftest_regcpy = (void (*)(void*, bool))dlsym(ref_handle, "difftest_regcpy");
ref_difftest_exec = (void (*)(uint64_t))dlsym(ref_handle, "difftest_exec");
ref_difftest_raise_intr = (void (*)(word_t))dlsym(ref_handle, "difftest_raise_intr");
ref_difftest_init = (void (*)(int))dlsym(ref_handle, "difftest_init");
// 检查必需函数
if (ref_difftest_memcpy == NULL || ref_difftest_regcpy == NULL ||
ref_difftest_exec == NULL || ref_difftest_init == NULL) {
printf("[DIFFTEST ERROR] Reference simulator missing required functions\n");
dlclose(ref_handle);
ref_handle = NULL;
return;
}
printf("[DIFFTEST INFO] Reference simulator loaded: %s\n", ref_so_file);
// 初始化参考模拟器
printf("[DIFFTEST] Initializing reference simulator...\n");
ref_difftest_init(1234); // 使用默认端口
// 同步内存(从 NPC 复制到 REF)
printf("[DIFFTEST] Copying %ld bytes of memory to reference...\n", img_size);
// 为内存复制分配缓冲区
uint8_t *buf = (uint8_t*)malloc(img_size);
if (buf == NULL) {
printf("[DIFFTEST ERROR] Failed to allocate memory buffer\n");
return;
}
// 从 NPC 内存中读取数据
printf("[DIFFTEST] Reading NPC memory...\n");
for (long i = 0; i < img_size; i += 4) {
uint32_t addr = 0x80000000 + i;
int ret = pmem_read(addr);
uint32_t data = (uint32_t)ret;
buf[i + 0] = (data >> 0) & 0xFF;
buf[i + 1] = (data >> 8) & 0xFF;
buf[i + 2] = (data >> 16) & 0xFF;
buf[i + 3] = (data >> 24) & 0xFF;
}
// 复制到参考模拟器
printf("[DIFFTEST] Copying memory to reference simulator...\n");
ref_difftest_memcpy(0x80000000, buf, img_size, DIFFTEST_TO_REF);
free(buf);
// 设置NPC初始状态(复位状态)
memset(&npc_cpu, 0, sizeof(npc_cpu));
npc_cpu.pc = 0x80000000; // 复位后的PC
// 复制NPC初始状态到参考模拟器
printf("[DIFFTEST] Copying initial CPU state to reference...\n");
ref_difftest_regcpy(&npc_cpu, DIFFTEST_TO_REF);
// 启用 DiffTest
diff_enabled = true;
skip_ref = false;
skip_dut_nr_inst = 0;
inst_count = 0;
printf("[DIFFTEST INFO] DiffTest initialized and enabled (img_size=%ld)\n", img_size);
}
// 主要的 DiffTest 步骤函数
void npc_difftest_step(uint32_t pc, uint32_t inst) {
if (!diff_enabled || ref_handle == NULL) {
return;
}
printf("[DIFFTEST] Executing reference for PC=0x%08x, INST=0x%08x\n", pc, inst);
// 第一步:让参考模拟器执行一条指令
ref_difftest_exec(1);
// 第二步:获取参考模拟器执行后的状态
CPU_state ref_state_after;
ref_difftest_regcpy(&ref_state_after, DIFFTEST_TO_DUT);
// 第三步:读取NPC执行后的状态
read_npc_state();
// 第四步:比较状态
bool match = true;
// 检查 PC
if (npc_cpu.pc != ref_state_after.pc) {
printf("[DIFFTEST ERROR] PC mismatch\n");
printf(" NPC PC after execution: 0x%08x\n", npc_cpu.pc);
printf(" REF PC after execution: 0x%08x\n", ref_state_after.pc);
match = false;
}
// 检查通用寄存器
for (int i = 1; i < 32; i++) { // 跳过x0
if (npc_cpu.gpr[i] != ref_state_after.gpr[i]) {
printf("[DIFFTEST ERROR] GPR x%02d mismatch\n", i);
printf(" NPC: 0x%08x (%d)\n", npc_cpu.gpr[i], npc_cpu.gpr[i]);
printf(" REF: 0x%08x (%d)\n", ref_state_after.gpr[i], ref_state_after.gpr[i]);
match = false;
break;
}
}
if (match) {
inst_count++;
printf("[DIFFTEST] Instruction %lu compared OK (PC=0x%08x -> 0x%08x)\n",
inst_count, pc, npc_cpu.pc);
} else {
printf("[DIFFTEST ERROR] at instruction %lu\n", inst_count + 1);
ebreak_handler(1);
}
}
// 启用/禁用 DiffTest
void npc_difftest_enable() {
if (ref_handle != NULL) {
diff_enabled = true;
printf("[DIFFTEST INFO] Enabled\n");
}
}
void npc_difftest_disable() {
diff_enabled = false;
printf("[DIFFTEST INFO] Disabled\n");
}
// 清理资源
void npc_difftest_close() {
if (ref_handle != NULL) {
dlclose(ref_handle);
ref_handle = NULL;
printf("[DIFFTEST INFO] Reference simulator unloaded\n");
}
printf("[DIFFTEST INFO] Total instructions compared: %lu\n", inst_count);
}
// ==================== DPI-C 接口 ====================
// DPI-C 初始化包装函数
void difftest_init_dpi(const char* ref_so_file, long img_size) {
npc_init_difftest(ref_so_file, img_size);
}
// DPI-C 检查包装函数
void difftest_check_dpi(uint32_t pc, uint32_t inst) {
npc_difftest_step(pc, inst);
}
// DPI-C 启用/禁用包装函数
void difftest_enable_dpi() {
npc_difftest_enable();
}
void difftest_disable_dpi() {
npc_difftest_disable();
}
csrc/main.c
/***************************************************************************************
* NPC 主程序 - 集成完整 SDB 和 DiffTest
***************************************************************************************/
#include "verilated.h"
#include "verilated_vcd_c.h"
#include "../obj_dir/Vtop.h"
#include <iostream>
#include <fstream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <getopt.h>
#include "../include/sdb.h"
#include "../include/npc.h"
#include "../include/trace.h"
#include "../include/difftest.h"
// 外部 C 函数
extern "C" {
// 寄存器访问
uint32_t debug_read_gpr(int index);
uint32_t debug_read_pc();
void set_gpr_ptr(uint32_t* ptr);
void set_top_ptr(void* ptr);
// 内存访问
int pmem_read(int raddr);
void pmem_write(int waddr, int wdata, char wmask);
void load_binary(const char* filename, uint32_t base_addr);
// 调试控制
void debug_set_single_step(int enable);
int debug_should_pause();
void ebreak_handler(int code);
// SDB 函数
void cpu_exec(uint64_t n);
void isa_reg_display();
// DiffTest 函数
void npc_init_difftest(const char* ref_so_file, long img_size);
void npc_difftest_enable();
void npc_difftest_disable();
void npc_difftest_close();
}
// 全局变量定义
VerilatedContext* contextp = NULL;
VerilatedVcdC* tfp = NULL;
static Vtop* top;
SimState sim_state = SIM_RUNNING;
static uint64_t step_count = 0;
// 在这里定义 npc_exit_flag,使其全局可见
int npc_exit_flag = 0;
// 声明外部变量和函数
extern "C" {
extern bool single_step_mode;
}
// 步进和波形记录
void step_and_dump_wave() {
top->clk = !top->clk;
top->eval();
contextp->timeInc(1);
if (tfp) {
tfp->dump(contextp->time());
}
}
// cpu_exec 实现(SDB 调用)
extern "C" void cpu_exec(uint64_t n) {
if (n == (uint64_t)-1) {
// 连续执行:设置 step_count = 0 表示无限执行
step_count = 0;
sim_state = SIM_RUNNING;
printf("Starting continuous execution...\n");
} else {
// 单步执行 N 条指令
step_count = n*2;
sim_state = SIM_RUNNING;
printf("Stepping %lu instructions...\n", n);
}
}
// 获取文件大小
long get_file_size(const char* filename) {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) {
return 0;
}
return file.tellg();
}
// 打印欢迎信息
void print_welcome() {
printf("\n");
printf(" _ __ ____ ___ \n");
printf(" / | / / / __ \\ / _ \\\n");
printf(" / |/ / / /_/ // __/\n");
printf(" / /| / / ____/ \\___/ \n");
printf("/_/ |_/ /_/ \n");
printf("\n");
printf("Welcome to riscv32-NPC!\n");
printf("For help, type \"help\"\n");
printf("\n");
}
// 参数解析
struct SimConfig {
const char* binary_file = nullptr;
const char* diff_so_file = nullptr;
const char* log_file = nullptr;
uint32_t entry_addr = 0x80000000;
bool dump_wave = true;
bool batch_mode = false;
bool difftest_enabled = true;
int max_cycles = 1000000;
} config;
// NEMU风格的参数解析
static int parse_args(int argc, char *argv[]) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " [OPTION...] IMAGE [args]\n\n";
std::cerr << "Options:\n";
std::cerr << " -b,--batch run with batch mode\n";
std::cerr << " -l,--log=FILE output log to FILE\n";
std::cerr << " -d,--diff=REF_SO run DiffTest with reference REF_SO\n";
std::cerr << " -nowave disable waveform dumping\n";
std::cerr << " -maxcycles=N maximum simulation cycles\n";
std::cerr << " -nodifftest disable DiffTest\n";
std::cerr << " -entry <addr> entry address (default: 0x80000000)\n";
std::cerr << " -help show this help message\n";
return -1;
}
// NEMU 风格的选项表
const struct option table[] = {
{"batch" , no_argument , NULL, 'b'},
{"log" , required_argument, NULL, 'l'},
{"diff" , required_argument, NULL, 'd'},
{"help" , no_argument , NULL, 'h'},
{0 , 0 , NULL, 0 },
};
int o;
while ((o = getopt_long(argc, argv, "-bhl:d:", table, NULL)) != -1) {
switch (o) {
case 'b':
config.batch_mode = true;
sdb_set_batch_mode();
break;
case 'l':
config.log_file = optarg;
break;
case 'd':
config.diff_so_file = optarg;
break;
case 1: // 非选项参数(通常是镜像文件名)
config.binary_file = optarg;
break;
default:
return -1;
}
}
// 检查其他参数
for (int i = optind; i < argc; i++) {
if (strcmp(argv[i], "-nowave") == 0) {
config.dump_wave = false;
} else if (strcmp(argv[i], "-maxcycles") == 0 && i + 1 < argc) {
config.max_cycles = atoi(argv[++i]);
} else if (strcmp(argv[i], "-nodifftest") == 0) {
config.difftest_enabled = false;
} else if (strcmp(argv[i], "-entry") == 0 && i + 1 < argc) {
config.entry_addr = strtoul(argv[++i], NULL, 0);
}
}
// 如果没有指定DiffTest参考模拟器,使用默认值
if (config.difftest_enabled && !config.diff_so_file) {
config.diff_so_file = "build/riscv32-nemu-interpreter-so";
}
return 0;
}
int main(int argc, char** argv) {
print_welcome();
// 解析参数(NEMU风格)
if (parse_args(argc, argv) < 0) {
return 1;
}
// 检查文件
if (!config.binary_file) {
std::cerr << "Error: No binary file specified\n";
return 1;
}
std::ifstream file(config.binary_file);
if (!file) {
std::cerr << "Error: Cannot open binary file: " << config.binary_file << std::endl;
return 1;
}
file.close();
// 获取二进制文件大小
long img_size = get_file_size(config.binary_file);
if (img_size <= 0) {
std::cerr << "Error: Invalid binary file size\n";
return 1;
}
// Trace配置
const char* itrace_file = config.log_file ? "itrace.log" : NULL;
const char* mtrace_file = config.log_file ? "mtrace.log" : NULL;
const char* ftrace_file = config.log_file ? "ftrace.log" : NULL;
// 初始化 Verilator
contextp = new VerilatedContext;
top = new Vtop{contextp};
set_top_ptr(top);
// 设置波形
if (config.dump_wave) {
contextp->traceEverOn(true);
tfp = new VerilatedVcdC;
top->trace(tfp, 99);
tfp->open("waveform.vcd");
}
// 初始化 SDB
init_sdb(tfp);
// 初始化 Trace 系统
init_trace(itrace_file, mtrace_file, ftrace_file);
// 加载二进制
printf("[NPC] Loading binary: %s (size: %ld bytes) to 0x%08x\n",
config.binary_file, img_size, config.entry_addr);
load_binary(config.binary_file, config.entry_addr);
// 先初始化 DiffTest(在复位前)
if (config.difftest_enabled && config.diff_so_file) {
printf("[DIFFTEST] Initializing with reference: %s\n", config.diff_so_file);
printf("[DIFFTEST] Image size: %ld bytes\n", img_size);
npc_init_difftest(config.diff_so_file, img_size);
} else if (config.difftest_enabled) {
printf("[DIFFTEST] No reference simulator specified\n");
printf("[DIFFTEST] Continuing without DiffTest\n");
config.difftest_enabled = false;
} else {
printf("[DIFFTEST] Disabled\n");
}
// 复位序列
printf("[NPC] Starting reset sequence...\n");
top->rst = 1;
for (int i = 0; i < 3; i++) {
step_and_dump_wave();
}
top->rst = 0;
// 额外运行几个周期以确保Verilog内部状态稳定
for (int i = 0; i < 2; i++) {
step_and_dump_wave();
}
printf("[NPC] Reset completed, Verilog state stabilized\n");
std::cout << "========================================" << std::endl;
std::cout << "NPC Simulator with SDB and DiffTest Started" << std::endl;
std::cout << "Binary: " << config.binary_file << std::endl;
std::cout << "DiffTest: " << (config.difftest_enabled ? "ENABLED" : "DISABLED") << std::endl;
std::cout << "Type 'help' for SDB commands" << std::endl;
std::cout << "========================================" << std::endl;
// 对于简单的测试程序,我们直接开始执行(跳过SDB交互)
if (!config.batch_mode) {
printf("[NPC] Auto-starting execution for test program...\n");
config.batch_mode = true;
sdb_set_batch_mode();
}
// 设置仿真状态为运行
sim_state = SIM_RUNNING;
step_count = 0;
// 主仿真循环
int cycle_count = 0;
while (cycle_count < config.max_cycles && !Verilated::gotFinish() && !npc_exit_flag) {
if (sim_state == SIM_PAUSED) {
// 进入 SDB
if (tfp) tfp->flush();
sdb_mainloop();
// 如果 SDB 设置了退出标志,退出循环
if (npc_exit_flag) {
break;
}
}
else if (sim_state == SIM_RUNNING) {
if (step_count > 0) {
// 单步执行模式:执行指定数量的周期
step_and_dump_wave();
cycle_count++;
step_count--;
if (step_count == 0) {
sim_state = SIM_PAUSED;
printf("Step execution completed. Total cycles: %d\n", cycle_count);
}
}
else if (step_count == 0) {
// 连续执行模式
step_and_dump_wave();
cycle_count++;
// 检查是否应该暂停(监视点触发或 ebreak)
if (check_watchpoints()) {
sim_state = SIM_PAUSED;
printf("Watchpoint triggered. Pausing at cycle %d\n", cycle_count);
}
// 定期输出进度
if (cycle_count % 1000 == 0) {
printf("[NPC] Cycle: %d, PC: 0x%08x\n", cycle_count, debug_read_pc());
}
}
}
else {
// 其他状态,等待
usleep(10000);
}
}
// 关闭DiffTest
if (config.difftest_enabled) {
npc_difftest_close();
}
// 关闭trace系统
close_trace();
// 关闭波形文件
if (tfp) {
tfp->close();
delete tfp;
}
// 清理 Verilator
delete top;
delete contextp;
// 输出总结
printf("========================================\n");
printf("NPC Simulation Finished\n");
printf("Total cycles: %d\n", cycle_count);
if (cycle_count >= config.max_cycles) {
printf("Reached maximum cycle limit\n");
}
printf("========================================\n");
return 0;
}
csrc/dpi_interface.c
// csrc/dpi_interface.c
#include <iostream>
#include <cstdlib>
#include <cstdint>
#include <fstream>
#include <vector>
#include "verilated.h"
#include "verilated_dpi.h"
#include "../obj_dir/Vtop.h"
#include "../include/trace.h"
#ifdef __cplusplus
extern "C" {
#endif
// ==================== 全局调试变量 ====================
static uint32_t *cpu_gpr = NULL; // 寄存器文件指针
static uint32_t *cpu_csr = NULL; // CSR寄存器指针
static Vtop *top_ptr = NULL; // Verilog顶层模块指针
static bool sdb_enabled = false; // SDB使能标志
static bool ebreak_triggered = false;
static int ebreak_exit_code = 0;
static uint32_t last_pc = 0;
static uint32_t last_inst = 0;
// 修改这里:去掉 static,使变量全局可见
bool single_step_mode = false;
// ==================== 寄存器访问 ====================
void set_gpr_ptr(const svOpenArrayHandle r) {
cpu_gpr = (uint32_t *)(((VerilatedDpiOpenVar*)r)->datap());
printf("[DPI] GPR pointer set: %p\n", cpu_gpr);
}
void set_top_ptr(void* ptr) {
top_ptr = (Vtop*)ptr;
printf("[DPI] Top pointer set: %p\n", top_ptr);
}
uint32_t debug_read_gpr(int index) {
if (cpu_gpr && index >= 0 && index < 32) {
// 调试:打印寄存器读取
// printf("[DPI DEBUG] Read GPR x%d: 0x%08x\n", index, cpu_gpr[index]);
return cpu_gpr[index];
}
printf("[DPI DEBUG] ERROR: cpu_gpr is NULL or index out of range\n");
return 0xDEADBEEF;
}
uint32_t debug_read_pc() {
if (top_ptr) {
uint32_t pc_value = top_ptr->pc;
// 调试:打印PC读取
// printf("[DPI DEBUG] Read PC: 0x%08x\n", pc_value);
return pc_value;
}
printf("[DPI DEBUG] ERROR: top_ptr is NULL\n");
return 0;
}
// ==================== 内存访问 ====================
static std::vector<uint8_t> pmem(64 * 1024 * 1024, 0); // 64MB内存
static uint32_t mem_base = 0x80000000;
void load_binary(const char* filename, uint32_t base_addr) {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (!file) {
std::cerr << "Error: Cannot open file " << filename << std::endl;
return;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
if (base_addr < mem_base) {
std::cerr << "Error: Base address below memory base" << std::endl;
return;
}
uint32_t offset = base_addr - mem_base;
if (offset + size > pmem.size()) {
std::cerr << "Error: Binary too large for memory" << std::endl;
return;
}
if (!file.read((char*)(pmem.data() + offset), size)) {
std::cerr << "Error: Failed to read file" << std::endl;
return;
}
printf("[DPI] Loaded %ld bytes to 0x%08x\n", size, base_addr);
file.close();
}
int pmem_read(int raddr) {
uint32_t addr = raddr;
if (addr < mem_base) {
printf("[DPI DEBUG] Warning: Reading from address below memory base: 0x%08x\n", addr);
return 0;
}
uint32_t offset = addr - mem_base;
if (offset + 4 > pmem.size()) {
printf("[DPI DEBUG] Warning: Address out of bounds: 0x%08x\n", addr);
return 0;
}
// 读取32位字(小端序)
uint32_t value = 0;
value |= pmem[offset + 0] << 0;
value |= pmem[offset + 1] << 8;
value |= pmem[offset + 2] << 16;
value |= pmem[offset + 3] << 24;
#ifdef CONFIG_MTRACE
mtrace(1, addr, value, 4); // type=1: Read, size=4 bytes
#endif
return value;
}
// 添加指令fetch trace
void trace_ifetch(uint32_t pc, uint32_t inst) {
last_pc = pc;
last_inst = inst;
#ifdef CONFIG_ITRACE
itrace(pc, inst);
#endif
#ifdef CONFIG_MTRACE
mtrace(0, pc, inst, 4); // type=0: Instruction Fetch
#endif
}
void trace_func_call(uint32_t pc, uint32_t target, uint32_t ret_addr) {
#ifdef CONFIG_FTRACE
//printf("[FTRACE DEBUG] Function call: pc=0x%08x -> target=0x%08x, return=0x%08x\n",
// pc, target, ret_addr);
ftrace(pc, target, ret_addr, 0);
#endif
}
void trace_func_ret(uint32_t pc, uint32_t target) {
#ifdef CONFIG_FTRACE
//printf("[FTRACE DEBUG] Function return: pc=0x%08x -> target=0x%08x\n",
// pc, target);
ftrace(pc, target, 0, 1);
#endif
}
// 添加指令执行trace(供Verilog调用)
void trace_exec(uint32_t pc, uint32_t inst) {
// 这里可以添加更多执行时的trace信息
}
void pmem_write(int waddr, int wdata, char wmask) {
uint32_t addr = waddr;
if (addr < mem_base) {
printf("[DPI DEBUG] Warning: Writing to address below memory base: 0x%08x\n", addr);
return;
}
uint32_t offset = addr - mem_base;
if (offset + 4 > pmem.size()) {
printf("[DPI DEBUG] Warning: Address out of bounds: 0x%08x\n", addr);
return;
}
// 根据写掩码写入字节
if (wmask & 0x1) pmem[offset + 0] = (wdata >> 0) & 0xFF;
if (wmask & 0x2) pmem[offset + 1] = (wdata >> 8) & 0xFF;
if (wmask & 0x4) pmem[offset + 2] = (wdata >> 16) & 0xFF;
if (wmask & 0x8) pmem[offset + 3] = (wdata >> 24) & 0xFF;
}
// ==================== 调试控制 ====================
void debug_set_single_step(int enable) {
single_step_mode = (enable != 0);
printf("[DPI] Single step mode: %s\n", single_step_mode ? "ON" : "OFF");
}
int debug_should_pause() {
return single_step_mode ? 1 : 0;
}
// ==================== ebreak处理 ====================
void ebreak_handler(int code) {
printf("\n" "========================================" "\n");
if (code == 0) {
printf("HIT GOOD TRAP\n");
printf("Program completed successfully!\n");
} else {
printf("HIT BAD TRAP\n");
printf("Exit code: %d\n", code);
printf("Program failed!\n");
}
printf("========================================" "\n");
// 如果SDB启用,不立即退出,进入调试器
if (sdb_enabled) {
printf("Press Enter to continue debugging...\n");
// 设置暂停标志,由SDB处理
} else {
Verilated::gotFinish(true);
}
}
// ==================== 波形控制 ====================
void flush_waveform() {
// 此函数将在SDB中调用以刷新波形
printf("[DPI] Waveform flushed\n");
}
#ifdef __cplusplus
}
#endif
vsrc/top.v
// vsrc/top.v
// ==================== 顶层模块定义 ====================
module top(
input clk,
input rst,
output reg [31:0] outdata,
output reg [31:0] pc
);
// 内部信号声明
wire [31:0] next_pc_reg;
wire [31:0] instruction;
// 寄存器文件信号
/* verilator lint_off UNOPTFLAT */
wire [4:0] rs1_addr, rs2_addr, rd_addr;
/* verilator lint_on UNOPTFLAT */
wire [31:0] rs1_data, rs2_data;
wire [31:0] rf_wdata;
wire rf_wen;
// 立即数信号
wire [31:0] imm_i, imm_u, imm_j;
// ALU信号
wire [31:0] alu_a, alu_b, alu_result;
wire [3:0] alu_op;
// 控制信号
wire is_auipc, is_lui, is_jal, is_jalr;
wire is_addi, is_sw, is_ebreak;
wire pc_src;
wire [31:0] jump_target;
// 写回数据选择
wire [1:0] wb_sel;
// 退出码信号
reg [31:0] exit_code;
// IDU输出信号
wire idu_ftrace_call;
wire idu_ftrace_ret;
wire [31:0] idu_ftrace_target;
wire [31:0] idu_ftrace_ret_addr;
// 避免重复记录的寄存器
reg [31:0] last_trace_pc;
reg last_trace_valid;
// ==================== DPI-C函数导入 ====================
import "DPI-C" function int pmem_read(input int raddr);
import "DPI-C" function void ebreak_handler(input int code);
import "DPI-C" function void set_top_ptr(input chandle ptr);
import "DPI-C" function void trace_ifetch(input int pc, input int inst);
import "DPI-C" function void trace_func_call(input int pc, input int target, input int ret_addr);
import "DPI-C" function void trace_func_ret(input int pc, input int target);
// DiffTest DPI-C函数
import "DPI-C" function void difftest_init_dpi(input string ref_so_file, input longint img_size);
import "DPI-C" function void difftest_check_dpi(input int pc, input int inst);
import "DPI-C" function void difftest_enable_dpi();
import "DPI-C" function void difftest_disable_dpi();
// ==================== 参数定义 ====================
parameter DIFFTEST_ENABLED = 1'b1;
// ==================== DiffTest相关寄存器 ====================
reg diff_enabled;
reg [63:0] diff_inst_count;
reg diff_initialized;
reg [31:0] last_check_pc;
reg [31:0] last_check_inst;
// ==================== PC相关信号 ====================
wire [31:0] pc_plus_4 = pc + 4;
// ==================== 初始化DiffTest ====================
initial begin
if (DIFFTEST_ENABLED) begin
diff_enabled = 1'b1; // 修复:改为阻塞赋值
$display("[DIFFTEST] Verilog side initialization complete");
end else begin
diff_enabled = 1'b0; // 修复:改为阻塞赋值
$display("[DIFFTEST] Disabled at compile time");
end
diff_inst_count = 0; // 修复:改为阻塞赋值
diff_initialized = 1'b0; // 修复:改为阻塞赋值
last_check_pc = 0; // 修复:改为阻塞赋值
last_check_inst = 0; // 修复:改为阻塞赋值
last_trace_pc = 0; // 修复:改为阻塞赋值
last_trace_valid = 0; // 修复:改为阻塞赋值
end
// ==================== PC更新逻辑 ====================
always @(posedge clk or posedge rst) begin
if (rst) begin
pc <= 32'h8000_0000;
exit_code <= 32'b0;
diff_initialized <= 1'b0; // 复位时清除初始化标志
$display("[DIFFTEST] Reset: PC set to 0x80000000");
end else begin
pc <= next_pc_reg;
// 如果当前指令是ebreak,保存a0寄存器值作为退出码
if (is_ebreak) begin
exit_code <= rs1_data;
$display("[DIFFTEST] Ebreak detected, exit code = %d", exit_code);
end
// 复位后第一个非复位周期标记为初始化完成
if (!diff_initialized) begin
diff_initialized <= 1'b1;
$display("[DIFFTEST] Now ready for instruction checking at time %0t", $time);
end
end
end
// 下一PC值选择
assign next_pc_reg = pc_src ? jump_target : pc_plus_4;
// ==================== 函数调用跟踪逻辑 ====================
always @(posedge clk) begin
if (!rst && diff_initialized) begin
// 避免重复记录相同的PC
if (last_trace_pc != pc || !last_trace_valid) begin
// 处理函数调用跟踪
if (idu_ftrace_call) begin
$display("[FTRACE DEBUG] Call: pc=0x%08x -> target=0x%08x, ret=0x%08x",
pc, idu_ftrace_target, idu_ftrace_ret_addr);
trace_func_call(pc, idu_ftrace_target, idu_ftrace_ret_addr);
end
// 处理函数返回跟踪
if (idu_ftrace_ret) begin
$display("[FTRACE DEBUG] Return: pc=0x%08x -> target=0x%08x",
pc, idu_ftrace_target);
trace_func_ret(pc, idu_ftrace_target);
end
last_trace_pc <= pc;
last_trace_valid <= 1'b1;
end
end else if (rst) begin
last_trace_pc <= 0;
last_trace_valid <= 0;
end
end
// ==================== 记录当前PC和指令 ====================
always @(posedge clk) begin
if (!rst) begin
last_check_pc <= pc;
last_check_inst <= instruction;
end
end
// ==================== DiffTest检查逻辑 ====================
// 在时钟下降沿进行检查,确保NPC状态已稳定
always @(negedge clk) begin
if (!rst && diff_enabled && diff_initialized) begin
// 检查当前指令是否有效(排除ebreak和无效指令)
if (!is_ebreak && last_check_inst !== 32'bx && last_check_inst != 0) begin
$display("[DIFFTEST] Check #%0d: Execute PC=0x%08x, INST=0x%08x, Next PC=0x%08x",
diff_inst_count + 1, last_check_pc, last_check_inst, pc);
// 调用DiffTest检查
difftest_check_dpi(last_check_pc, last_check_inst);
// 增加计数器
diff_inst_count <= diff_inst_count + 1;
end
end
end
// ==================== 模块实例化 ====================
ysyx_25110281_ifu ifu(
.pc(pc),
.clk(clk),
.ins(instruction)
);
ysyx_25110281_idu idu(
.ins(instruction),
.pc(pc),
.rs1_data(rs1_data),
// 寄存器地址输出
.rs1_addr(rs1_addr),
.rs2_addr(rs2_addr),
.rd_addr(rd_addr),
// 立即数输出
.imm_i(imm_i),
.imm_u(imm_u),
.imm_j(imm_j),
// 控制信号输出
.is_auipc(is_auipc),
.is_lui(is_lui),
.is_jal(is_jal),
.is_jalr(is_jalr),
.is_addi(is_addi),
.is_sw(is_sw),
.is_ebreak(is_ebreak),
// ALU控制
.alu_op(alu_op),
// PC控制
.pc_src(pc_src),
.jump_target(jump_target),
// 写回控制
.wb_sel(wb_sel),
.rf_wen(rf_wen),
// 函数调用跟踪信号
.ftrace_call(idu_ftrace_call),
.ftrace_ret(idu_ftrace_ret),
.ftrace_target(idu_ftrace_target),
.ftrace_ret_addr(idu_ftrace_ret_addr)
);
RegisterFile regfile(
.clk(clk),
.rst(rst),
.raddr1(rs1_addr),
.raddr2(rs2_addr),
.rdata1(rs1_data),
.rdata2(rs2_data),
.waddr(rd_addr),
.wdata(rf_wdata),
.wen(rf_wen)
);
ysyx_25110281_alu alu(
.a(alu_a),
.b(alu_b),
.op(alu_op),
.result(alu_result)
);
// ==================== 数据通路连接 ====================
// ALU输入A选择:auipc时选PC,其他选rs1
assign alu_a = is_auipc ? pc : rs1_data;
// ALU输入B选择:立即数 - 修复:auipc和lui使用imm_u,其他使用imm_i
assign alu_b = (is_auipc | is_lui) ? imm_u : imm_i;
// 写回数据选择
assign rf_wdata = (wb_sel == 2'b00) ? alu_result :
(wb_sel == 2'b01) ? pc_plus_4 :
(wb_sel == 2'b10) ? imm_u :
32'b0;
// ==================== ebreak处理 ====================
always @(posedge clk) begin
if (is_ebreak && !rst) begin
$display("Time=%0t: Detected ebreak, exit code = %0d", $time, exit_code);
ebreak_handler(exit_code);
end
end
// ==================== 最终化 ====================
final begin
if (DIFFTEST_ENABLED) begin
$display("[DIFFTEST] Final statistics:");
$display("[DIFFTEST] Total instructions compared: %0d", diff_inst_count);
if (diff_inst_count == 0) begin
$display("[DIFFTEST] WARNING: No instructions were compared!");
$display("[DIFFTEST] Possible issues:");
$display("[DIFFTEST] - diff_enabled: %b", diff_enabled);
$display("[DIFFTEST] - diff_initialized: %b", diff_initialized);
$display("[DIFFTEST] - Total simulation time: %0t", $time);
end else if (diff_inst_count < 3) begin
$display("[DIFFTEST] NOTE: Only %0d instructions were compared", diff_inst_count);
$display("[DIFFTEST] This may be normal for short test programs");
end else begin
$display("[DIFFTEST] SUCCESS: Compared %0d instructions", diff_inst_count);
end
end
end
endmodule
Makefile
# NPC Makefile - 完整修复版本
CFLAGS += -I./include -g -O2 -Wall
LDFLAGS += -lreadline -ldl
# 配置选项
CONFIG_ITRACE ?= y
CONFIG_MTRACE ?= y
CONFIG_FTRACE ?= y
CONFIG_DIFFTEST ?= y # 添加DiffTest配置
# NEMU参考模拟器路径
NEMU_REF_SO = ../nemu/build/riscv32-nemu-interpreter-so
BUILD_REF_SO = build/riscv32-nemu-interpreter-so
# 添加Capstone相关配置
ifneq ($(CONFIG_ITRACE),)
# Capstone配置
CAPSTONE_DIR = tools/capstone/repo
CAPSTONE_LIB = $(CAPSTONE_DIR)/libcapstone.so
CAPSTONE_INC = $(CAPSTONE_DIR)/include
# 确保Capstone库已构建
$(CAPSTONE_LIB):
@echo "Building Capstone library..."
$(MAKE) -C tools/capstone/repo
@echo "Capstone library built successfully"
CFLAGS += -I$(CAPSTONE_INC) -DCONFIG_ITRACE
# 使用绝对路径链接库
CAPSTONE_ABS_DIR = $(realpath $(CAPSTONE_DIR))
LDFLAGS += -L$(CAPSTONE_ABS_DIR) -Wl,-rpath,$(CAPSTONE_ABS_DIR) -lcapstone
endif
ifneq ($(CONFIG_MTRACE),)
CFLAGS += -DCONFIG_MTRACE
endif
ifneq ($(CONFIG_FTRACE),)
CFLAGS += -DCONFIG_FTRACE
endif
# 添加DiffTest配置
ifneq ($(CONFIG_DIFFTEST),)
CFLAGS += -DCONFIG_DIFFTEST
# 添加dlopen支持
LDFLAGS += -ldl
endif
# 源文件
VSRC = $(wildcard ./vsrc/*.v)
CSRC = $(wildcard ./csrc/*.c)
# 根据配置排除或包含trace源文件
ifeq ($(CONFIG_ITRACE)$(CONFIG_MTRACE)$(CONFIG_FTRACE),)
CSRC := $(filter-out ./csrc/trace.c, $(CSRC))
endif
# 添加difftest.c到源文件列表
ifneq ($(CONFIG_DIFFTEST),)
CSRC += ./csrc/difftest.c
endif
# 默认目标
all: build
# 检查并复制NEMU库
$(BUILD_REF_SO):
@if [ -f "$(NEMU_REF_SO)" ]; then \
echo "Copying NEMU reference simulator..."; \
mkdir -p build; \
cp $(NEMU_REF_SO) $(BUILD_REF_SO); \
echo "Copied NEMU reference to $(BUILD_REF_SO)"; \
else \
echo "WARNING: NEMU reference simulator not found at $(NEMU_REF_SO)"; \
echo "DiffTest will be disabled at runtime"; \
echo ""; \
echo "To build NEMU:"; \
echo " cd ../nemu && make isa=riscv32 nemu-interpreter-so"; \
fi
# 构建 NPC 仿真器
build: $(CAPSTONE_LIB) $(BUILD_REF_SO)
@echo "Building NPC simulator with trace and DiffTest support..."
@echo "Config: ITRACE=$(CONFIG_ITRACE) MTRACE=$(CONFIG_MTRACE) FTRACE=$(CONFIG_FTRACE) DIFFTEST=$(CONFIG_DIFFTEST)"
@verilator -Wno-fatal -Wno-WIDTH \
--timing \
$(VSRC) $(CSRC) \
--top-module top \
--cc \
--trace \
--exe \
--build \
-CFLAGS "$(CFLAGS)" \
-LDFLAGS "$(LDFLAGS)" \
--Mdir ./obj_dir
@echo "Build completed: ./obj_dir/Vtop"
# 运行
run: build
@if [ -z "$(ARGS)" ]; then \
echo "========================================"; \
echo "Error: No binary file specified"; \
echo ""; \
echo "Usage: make run ARGS=\"<path/to/binary.bin> [options]\""; \
echo ""; \
echo "Example:"; \
echo " make run ARGS=\"test.bin\""; \
echo " make run ARGS=\"test.bin -batch\""; \
echo " make run ARGS=\"test.bin -d build/riscv32-nemu-interpreter-so\""; \
echo "========================================"; \
exit 1; \
fi
@echo "========================================"
@echo "Running NPC with: $(ARGS)"
@echo "========================================"
# 检查二进制文件
@BINARY=$$(echo $(ARGS) | cut -d' ' -f1); \
if [ ! -f "$$BINARY" ]; then \
echo "Error: File not found: $$BINARY"; \
exit 1; \
fi
# 运行仿真器
@./obj_dir/Vtop $(ARGS) || true
@echo "========================================"
@echo "NPC simulation finished"
@echo "========================================"
# 批处理模式
batch: build
@if [ -z "$(ARGS)" ]; then \
echo "Usage: make batch ARGS=<binary.bin>"; \
exit 1; \
fi
@./obj_dir/Vtop $(ARGS) -batch
# 查看波形
wave:
@if [ -f "waveform.vcd" ]; then \
gtkwave waveform.vcd & \
else \
echo "No waveform found. Run 'make run' first."; \
fi
# 清理
clean:
rm -rf obj_dir *.vcd *.log test.bin build/riscv32-nemu-interpreter-so
# 创建简单测试程序
test.bin:
@echo "Creating test program using Python..."
@python3 -c "with open('test.bin', 'wb') as f: f.write(b'\x13\x01\x10\x00\x93\x02\xa0\x00\x73\x00\x10\x00')"
@echo "Created test.bin"
@echo "Content verification:"
@hexdump -C test.bin
# 测试 SDB
test: test.bin build
@echo "========================================"
@echo "Testing SDB..."
@echo "Instructions:"
@echo " 1. addi x1, x0, 1 # x1 = 1"
@echo " 2. addi x2, x0, 10 # x2 = 10"
@echo " 3. ebreak # breakpoint"
@echo "========================================"
@./obj_dir/Vtop test.bin
# 测试DiffTest - 使用简单的test.bin
test-diff: test.bin build
@echo "========================================"
@echo "Testing DiffTest with simple test program"
@echo "========================================"
@if [ -f "$(BUILD_REF_SO)" ]; then \
./obj_dir/Vtop test.bin -d $(BUILD_REF_SO) || true; \
else \
echo "NEMU reference not found, running without DiffTest"; \
./obj_dir/Vtop test.bin -nodifftest || true; \
fi
.PHONY: all build run batch wave clean test test-diff

浙公网安备 33010602011771号