feat(npc): difftest execution abstraction layer

This commit is contained in:
xinyangli 2024-04-10 20:12:41 +08:00
parent e828e140cd
commit 89847cfdb4
Signed by: xin
SSH key fingerprint: SHA256:qZ/tzd8lYRtUFSrfBDBMcUqV4GHKxqeqRA3huItgvbk
17 changed files with 1076 additions and 276 deletions

View file

@ -11,14 +11,6 @@
#include <stdexcept>
#include <string>
const std::map<std::string, int> riscv32_regs_by_name{
{"$0", 0}, {"ra", 1}, {"sp", 2}, {"gp", 3}, {"tp", 4}, {"t0", 5},
{"t1", 6}, {"t2", 7}, {"s0", 8}, {"s1", 9}, {"a0", 10}, {"a1", 11},
{"a2", 12}, {"a3", 13}, {"a4", 14}, {"a5", 15}, {"a6", 16}, {"a7", 17},
{"s2", 18}, {"s3", 19}, {"s4", 20}, {"s5", 21}, {"s6", 22}, {"s7", 23},
{"s8", 24}, {"s9", 25}, {"s10", 26}, {"s11", 27}, {"t3", 28}, {"t4", 29},
{"t5", 30}, {"t6", 31}};
template <typename T, std::size_t nr> class _RegistersBase {
std::array<T, nr> regs;
T pc;

View file

@ -1,130 +0,0 @@
#ifndef _DIFFTEST_DIFFTEST_H_
#define _DIFFTEST_DIFFTEST_H_
#include <cassert>
#include <components.hpp>
#include <cstdint>
#include <cstdlib>
#include <dlfcn.h>
#include <filesystem>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
using paddr_t = uint32_t;
enum { DIFFTEST_FROM_REF, DIFFTEST_TO_REF };
struct DifftestInterface {
using memcpy_t = void (*)(paddr_t, void *, size_t, bool);
using regcpy_t = void (*)(void *, bool);
using exec_t = void (*)(uint64_t);
using init_t = void (*)(int);
std::function<void(paddr_t, void *, size_t, bool)> memcpy;
std::function<void(void *, bool)> regcpy;
std::function<void(uint64_t)> exec;
std::function<void(int)> init;
DifftestInterface(memcpy_t memcpy, regcpy_t regcpy, exec_t exec, init_t init)
: memcpy(memcpy), regcpy(regcpy), exec(exec), init(init){};
// using fs = std::filesystem::path;
DifftestInterface(std::filesystem::path lib_file) {
void *handle = dlopen(lib_file.c_str(), RTLD_LAZY);
assert(handle != nullptr);
memcpy = (memcpy_t)dlsym(handle, "difftest_memcpy");
assert(memcpy);
regcpy = (regcpy_t)dlsym(handle, "difftest_regcpy");
assert(regcpy);
exec = (exec_t)dlsym(handle, "difftest_exec");
assert(exec);
init = (init_t)dlsym(handle, "difftest_init");
assert(init);
}
};
template <typename S> class Difftest {
const DifftestInterface &ref;
std::unique_ptr<S> ref_state;
const DifftestInterface &dut;
std::unique_ptr<S> dut_state;
public:
Difftest(const DifftestInterface &dut, const DifftestInterface &ref,
void *mem, size_t n, std::unique_ptr<S> ref_state = nullptr,
std::unique_ptr<S> dut_state = nullptr)
: ref(ref), dut(dut), ref_state(std::move(ref_state)),
dut_state(std::move(dut_state)) {
if (ref_state == nullptr)
this->ref_state = std::make_unique<S>();
if (dut_state == nullptr)
this->dut_state = std::make_unique<S>();
ref.init(0);
dut.init(0);
fetch_state();
paddr_t reset_vector = 0x80000000;
ref.memcpy(reset_vector, mem, n, DIFFTEST_TO_REF);
dut.memcpy(reset_vector, mem, n, DIFFTEST_TO_REF);
};
void fetch_state() {
ref.regcpy(ref_state.get(), DIFFTEST_FROM_REF);
dut.regcpy(dut_state.get(), DIFFTEST_FROM_REF);
}
bool step(uint64_t n) {
ref.exec(n);
dut.exec(n);
fetch_state();
return *ref_state == *dut_state;
}
friend std::ostream &operator<<(std::ostream &os, const Difftest<S> &d) {
os << "REF state:\n"
<< *d.ref_state << "DUT state:\n"
<< *d.dut_state << std::endl;
return os;
}
};
template <typename R, size_t nr_reg> struct CPUStateBase {
R reg[nr_reg] = {0};
paddr_t pc = 0x80000000;
static const std::map<std::string, int> inline regs_by_name =
riscv32_regs_by_name;
CPUStateBase() {
for (int i = 0; i < nr_reg; i++)
reg[i] = 0;
}
bool operator==(const CPUStateBase &other) const {
if (pc != other.pc)
return false;
for (int i = 0; i < nr_reg; ++i) {
if (reg[i] != other.reg[i])
return false;
}
return true;
}
bool operator!=(const CPUStateBase &other) const {
return !(*this == other); // Reuse the == operator for != implementation
}
/* This does not update the register!!! */
R at(std::string name) { return reg[regs_by_name.at(name)]; }
};
template <typename R, size_t nr_reg>
std::ostream &operator<<(std::ostream &os, const CPUStateBase<R, nr_reg> &cpu) {
os << "PC: " << std::hex << cpu.pc << std::endl;
for (int i = 0; i < nr_reg; i++) {
os << "reg " << std::dec << std::setw(2) << i << ":" << std::hex
<< std::setw(10) << cpu.reg[i];
if (i % 4 == 3) {
os << std::endl;
} else {
os << " | ";
}
}
return os;
}
#endif

View file

@ -0,0 +1,93 @@
#ifndef _DIFFTEST_DIFFTEST_H_
#define _DIFFTEST_DIFFTEST_H_
#include "disasm.hpp"
#include "types.h"
#include <cassert>
#include <components.hpp>
#include <cstdint>
#include <cstdlib>
#include <dlfcn.h>
#include <filesystem>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <ostream>
#include <stdexcept>
#include <trm_interface.hpp>
Disassembler d{"riscv32-linux-pc-gnu"};
using paddr_t = uint32_t;
struct DifftestTrmInterface : public TrmInterface {
TrmInterface &dut;
TrmInterface &ref;
DifftestTrmInterface(TrmInterface &dut, TrmInterface &ref, void *mem,
size_t mem_size)
: dut(dut), ref(ref) {
init = [this, mem, mem_size](int n) {
this->ref.init(n);
this->dut.init(n);
paddr_t reset_vector = 0x80000000;
this->ref.memcpy(reset_vector, mem, mem_size, TRM_TO_MACHINE);
this->dut.memcpy(reset_vector, mem, mem_size, TRM_TO_MACHINE);
fetch_state();
};
exec = [this](uint64_t n) {
while (n--) {
word_t pc = this->ref.at("pc");
word_t inst = this->ref.at(pc);
std::cout << d.disassemble(pc, (uint8_t *)&inst, WORD_BYTES)
<< std::endl;
this->ref.exec(1);
this->dut.exec(1);
this->ref.fetch_state();
this->dut.fetch_state();
if (*(CPUState *)this->ref.cpu_state !=
*(CPUState *)this->dut.cpu_state) {
throw std::runtime_error("Difftest failed");
}
}
};
// NOTE: Different from normal Trm, we copy 2 * sizeof(CPUState) to/from p,
// which represents ref_state and dut state
regcpy = [this](void *p, bool direction) {
// this->ref.regcpy(p, direction);
// this->dut.regcpy(p, direction);
};
memcpy = [this](paddr_t paddr, void *p, size_t n, bool direction) {
this->dut.memcpy(paddr, p, n, direction);
this->ref.memcpy(paddr, (uint8_t *)p + n, n, direction);
};
}
word_t at(std::string name) const override {
if (name.empty()) {
throw std::runtime_error("Empty register name");
} else if (name[0] == 'r') {
std::cout << name.substr(1) << std::endl;
this->ref.at(name.substr(1));
} else if (name[0] == 'd') {
this->dut.at(name.substr(1));
} else {
throw std::runtime_error("Register name provided to difftest interface "
"must start with r or d.");
}
return 0;
}
word_t at(paddr_t addr) const override {
std::cout << ref.at(addr) << "\t" << dut.at(addr) << std::endl;
return dut.at(addr);
}
void print(std::ostream &os) const override {
os << "REF state:\n"
<< *(CPUState *)ref.cpu_state << "DUT state:\n"
<< *(CPUState *)dut.cpu_state << std::endl;
}
};
#endif

View file

@ -0,0 +1,139 @@
#ifndef _NPC_TRM_INTERFACE_HEADER_FILE_
#define _NPC_TRM_INTERFACE_HEADER_FILE_
#include <dlfcn.h>
#include <filesystem>
#include <functional>
#include <stdexcept>
#include <string>
#include <types.h>
template <typename R, size_t nr_reg> struct CPUStateBase {
R reg[nr_reg] = {0};
word_t pc = 0x80000000;
static const std::map<std::string, int> inline regs_by_name =
riscv32_regs_by_name;
CPUStateBase() {
for (int i = 0; i < nr_reg; i++)
reg[i] = 0;
}
bool operator==(const CPUStateBase &other) const {
if (pc != other.pc)
return false;
for (int i = 0; i < nr_reg; ++i) {
if (reg[i] != other.reg[i])
return false;
}
return true;
}
bool operator!=(const CPUStateBase &other) const {
return !(*this == other); // Reuse the == operator for != implementation
}
/* This does not update the register!!! */
R at(std::string name) {
return name == "pc" ? pc : reg[regs_by_name.at(name)];
}
uint32_t reg_str2val(const char *name, bool *success) {
try {
*success = true;
return this->at(name);
} catch (std::runtime_error) {
*success = false;
return 0;
}
}
};
template <typename R, size_t nr_reg>
std::ostream &operator<<(std::ostream &os, const CPUStateBase<R, nr_reg> &cpu) {
os << "PC: " << std::hex << cpu.pc << std::endl;
for (int i = 0; i < nr_reg; i++) {
os << "reg " << std::dec << std::setw(2) << i << ":" << std::hex
<< std::setw(10) << cpu.reg[i];
if (i % 4 == 3) {
os << std::endl;
} else {
os << " | ";
}
}
return os;
}
using CPUState = CPUStateBase<word_t, REG_COUNT>;
enum { TRM_FROM_MACHINE, TRM_TO_MACHINE };
class TrmInterface {
protected:
using memcpy_t = void (*)(paddr_t, void *, size_t, bool);
using regcpy_t = void (*)(void *, bool);
using exec_t = void (*)(uint64_t);
using init_t = void (*)(int);
std::function<void(void *, bool)> regcpy;
public:
std::function<void(uint64_t)> exec;
std::function<void(int)> init;
// TODO: paddr_t can probably changed to (void *)?
std::function<void(paddr_t, void *, size_t, bool)> memcpy;
// Managed by callee
void *cpu_state;
TrmInterface() {}
TrmInterface(memcpy_t f_memcpy, regcpy_t f_regcpy, exec_t f_exec,
init_t f_init, void *cpu_state)
: memcpy(f_memcpy), regcpy(f_regcpy), exec(f_exec), init(f_init),
cpu_state(cpu_state) {}
void fetch_state() { this->regcpy(cpu_state, TRM_FROM_MACHINE); }
void push_state() { this->regcpy(cpu_state, TRM_TO_MACHINE); }
virtual word_t at(std::string) const = 0;
virtual word_t at(word_t addr) const = 0;
virtual void print(std::ostream &os) const = 0;
};
struct RefTrmInterface : TrmInterface {
RefTrmInterface(std::filesystem::path lib_file) {
void *handle = dlopen(lib_file.c_str(), RTLD_LAZY);
if (handle == nullptr) {
throw std::runtime_error("Failed to open diff library file");
};
memcpy = (memcpy_t)dlsym(handle, "difftest_memcpy");
if (handle == nullptr) {
throw std::runtime_error("Failed to find `difftest_memcpy`");
};
regcpy = (regcpy_t)dlsym(handle, "difftest_regcpy");
if (handle == nullptr) {
throw std::runtime_error("Failed to find `difftest_regcpy`");
};
exec = (exec_t)dlsym(handle, "difftest_exec");
if (handle == nullptr) {
throw std::runtime_error("Failed to find `difftest_exec`");
};
init = (init_t)dlsym(handle, "difftest_init");
if (handle == nullptr) {
throw std::runtime_error("Failed to find `difftest_init`");
};
cpu_state = new CPUState{};
}
~RefTrmInterface() { delete (CPUState *)cpu_state; }
word_t at(std::string name) const override {
return ((CPUState *)cpu_state)->at(name);
}
word_t at(paddr_t addr) const override {
word_t buf;
this->memcpy(addr, &buf, sizeof(word_t), TRM_FROM_MACHINE);
return buf;
}
void print(std::ostream &os) const override { os << *(CPUState *)cpu_state; }
};
#endif

View file

@ -8,6 +8,7 @@ static const word_t WORD_T_MAX = UINT32_MAX;
static const sword_t SWORD_T_MAX = INT32_MAX;
static const sword_t SWORD_T_MIN = INT32_MIN;
#define WORD_BYTES 4
#define REG_COUNT 32
#define FMT_WORD "0x%08x"
typedef uint32_t vaddr_t;
@ -16,8 +17,16 @@ typedef uint32_t paddr_t;
typedef uint16_t ioaddr_t;
#ifdef __cplusplus
#include <difftest.hpp>
using CPUState = CPUStateBase<word_t, 32>;
#include <map>
#include <string>
const std::map<std::string, int> riscv32_regs_by_name{
{"$0", 0}, {"ra", 1}, {"sp", 2}, {"gp", 3}, {"tp", 4}, {"t0", 5},
{"t1", 6}, {"t2", 7}, {"s0", 8}, {"s1", 9}, {"a0", 10}, {"a1", 11},
{"a2", 12}, {"a3", 13}, {"a4", 14}, {"a5", 15}, {"a6", 16}, {"a7", 17},
{"s2", 18}, {"s3", 19}, {"s4", 20}, {"s5", 21}, {"s6", 22}, {"s7", 23},
{"s8", 24}, {"s9", 25}, {"s10", 26}, {"s11", 27}, {"t3", 28}, {"t4", 29},
{"t5", 30}, {"t6", 31}};
#endif
#endif

View file

@ -19,8 +19,7 @@ public:
~Tracer() { m_trace->close(); }
/**
* Dump signals to waveform file. Must be called once after every top->eval()
* call.
* @brief: Dump signals to waveform file. Must be called once after every top->eval() call.
*/
void update() { m_trace->dump(cycle++); }
};