feat(npc): difftest execution abstraction layer
This commit is contained in:
parent
e828e140cd
commit
89847cfdb4
17 changed files with 1076 additions and 276 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
93
npc/include/trm_difftest.hpp
Normal file
93
npc/include/trm_difftest.hpp
Normal 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
|
139
npc/include/trm_interface.hpp
Normal file
139
npc/include/trm_interface.hpp
Normal 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
|
|
@ -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
|
|
@ -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++); }
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue