npc,refactor: remove original difftest wrapper, cleanup code
Some checks failed
Build abstract machine with nix / build-abstract-machine (push) Failing after 53s
Run CTests within npc / npc-test (push) Failing after 48s

This commit is contained in:
xinyangli 2024-08-02 11:18:52 +08:00
parent 85d7840804
commit de9f770d08
Signed by: xin
SSH key fingerprint: SHA256:qZ/tzd8lYRtUFSrfBDBMcUqV4GHKxqeqRA3huItgvbk
10 changed files with 80 additions and 351 deletions

View file

@ -1,20 +1,19 @@
# -- Add an always run target to generate verilog files with sbt/bloop,
# as we don't know if the result files will be different from cmake
# NOTE: Must reconfigure if we add new files in SCALA_CORE directory
# -- Add an always run target to generate verilog files with sbt/bloop, as we
# don't know if the result files will be different from cmake NOTE: Must
# reconfigure if we add new files in SCALA_CORE directory
file(GLOB_RECURSE SCALA_CORE_SOURCES "${SCALA_CORE}/src/main/scala/*.scala")
file(GLOB_RECURSE SCALA_CORE_RESOURCES "${SCALA_CORE}/src/main/resources/*")
message(STATUS "Found scala source file: ${SCALA_CORE_SOURCES}")
set(CHISEL_DEPENDENCY ${SCALA_CORE_SOURCES} ${SCALA_CORE_RESOURCES} ${SCALA_CORE}/build.sbt)
set(CHISEL_DEPENDENCY ${SCALA_CORE_SOURCES} ${SCALA_CORE_RESOURCES}
${SCALA_CORE}/build.sbt)
if(BUILD_USE_BLOOP)
message(STATUS "Building core using bloop")
set(CHISEL_TARGET bloop_${TOPMODULE})
set(CHISEL_TEST_TARGET bloop_${TOPMODULE}_test)
# Export sbt build config to bloop
if(NOT EXISTS ${SCALA_CORE}/.bloop)
execute_process(
COMMAND sbt bloopInstall
WORKING_DIRECTORY ${SCALA_CORE}
)
execute_process(COMMAND sbt bloopInstall WORKING_DIRECTORY ${SCALA_CORE})
endif()
string(REPLACE " " ";" CHISEL_EMIT_ARGS_LIST ${CHISEL_EMIT_ARGS})
list(TRANSFORM CHISEL_EMIT_ARGS_LIST PREPEND "--args;")
@ -24,14 +23,11 @@ if(BUILD_USE_BLOOP)
WORKING_DIRECTORY ${SCALA_CORE}
DEPENDS ${CHISEL_DEPENDENCY}
COMMAND_EXPAND_LISTS
COMMENT "Run bloop from CMake"
)
# add_test(
# NAME bloop_${TOPMODULE}_test
# COMMAND bloop test
# WORKING_DIRECTORY ${SCALA_CORE}
# )
COMMENT "Run bloop from CMake")
# add_test( NAME bloop_${TOPMODULE}_test COMMAND bloop test WORKING_DIRECTORY
# ${SCALA_CORE} )
else()
message(STATUS "Building core using sbt")
set(CHISEL_TARGET sbt_${TOPMODULE})
set(CHISEL_TEST_TARGET sbt_${TOPMODULE}_test)
add_custom_command(
@ -40,19 +36,16 @@ else()
WORKING_DIRECTORY ${SCALA_CORE}
DEPENDS ${CHISEL_DEPENDENCY}
VERBATIM
COMMENT "Run sbt from CMake"
)
COMMENT "Run sbt from CMake")
add_test(
NAME sbt_${TOPMODULE}_test
COMMAND sbt test
WORKING_DIRECTORY ${SCALA_CORE}
)
WORKING_DIRECTORY ${SCALA_CORE})
endif()
if(NOT EXISTS ${CHISEL_OUTPUT_TOPMODULE})
# Probably cold build, generate verilog at configure time to produce top module file
execute_process(
COMMAND sbt "run ${CHISEL_EMIT_ARGS}"
WORKING_DIRECTORY ${SCALA_CORE}
)
# Probably cold build, generate verilog at configure time to produce top
# module file
execute_process(COMMAND sbt "run ${CHISEL_EMIT_ARGS}"
WORKING_DIRECTORY ${SCALA_CORE})
endif()

View file

@ -6,7 +6,8 @@ import shapeless.{HNil, ::}
class ALUControlInterface extends Bundle {
object OpSelect extends ChiselEnum {
val aOpAdd, aOpSub, aOpNot, aOpAnd, aOpOr, aOpXor, aOpSlt, aOpSltu, aOpSll, aOpSrl, aOpSra = Value
val aOpAdd, aOpSub, aOpNot, aOpAnd, aOpOr, aOpXor, aOpSlt, aOpSltu, aOpSll,
aOpSrl, aOpSra = Value
}
object SrcASelect extends ChiselEnum {
val aSrcARs1, aSrcAPc, aSrcAZero = Value
@ -54,7 +55,8 @@ class ALU[T <: UInt](tpe: T) extends Module {
import control.OpSelect._
out.result := MuxLookup(control.op, 0.U)(Seq(
out.result := MuxLookup(control.op, 0.U)(
Seq(
aOpAdd -> add,
aOpSub -> sub,
aOpNot -> not,
@ -66,7 +68,8 @@ class ALU[T <: UInt](tpe: T) extends Module {
aOpSll -> sll,
aOpSrl -> srl,
aOpSra -> sra.asUInt
))
)
)
}
object ALU {

View file

@ -1,13 +1,15 @@
package flow.components
import chisel3._
import chisel3.experimental.noPrefix
import chisel3.util.HasBlackBoxPath
import chisel3.util.HasBlackBoxResource
import chisel3.util.log2Ceil
import chisel3.experimental.noPrefix
import scala.collection.SeqMap
import flow.components
import shapeless.{HNil, ::}
import shapeless.::
import shapeless.HNil
import scala.collection.SeqMap
class RamControlInterface(addrWidth: Int) extends Bundle {
val valid = Input(Bool())

View file

@ -11,9 +11,6 @@ void Config::cli_parse(int argc, char **argv) {
"Memory file is in text format");
app.add_option("--wav", wavefile, "output .vcd file path");
app.add_flag("-i", interactive, "Launch sdb for interactive session");
app.add_option("--diff-lib", lib_ref,
"Dynamic library file of difftest reference")
->check(CLI::ExistingFile);
try {
app.parse(argc, argv);

View file

@ -14,32 +14,48 @@
#include <string>
template <typename T, std::size_t nr> class _RegistersBase {
std::array<T, nr> regs;
T pc;
std::shared_ptr<spdlog::logger> logger = spdlog::stdout_color_mt("registers");
virtual T fetch_pc() const;
virtual T fetch_reg(std::size_t id) const;
virtual T fetch_pc() const = 0;
virtual T fetch_reg(std::size_t id) const = 0;
public:
T operator[](size_t id) const { return fetch_reg(id); }
T get_pc() const { return fetch_pc(); }
void update() {
for (int i = 0; i < regs.size(); i++) {
regs[i] = fetch_reg(i);
}
}
};
template <std::size_t n> class Memory {
paddr_t pmem_start, pmem_end;
public:
std::array<uint8_t, n> mem;
// TODO: Read memory file before init and use memcpy to initialize memory.
Memory(std::filesystem::path filepath, bool is_binary, paddr_t pmem_start,
paddr_t pmem_end)
: pmem_start(pmem_start), pmem_end(pmem_end) {
read_memory(filepath, is_binary);
}
const word_t &operator[](std::size_t addr) { return this->read(addr); }
void transfer(paddr_t addr, uint8_t data[], size_t len, bool is_write) {
if (is_write) {
// memcpy(guest_to_host(addr), data, len);
size_t offset = (addr - pmem_start);
std::copy(data, data + len, &mem[offset]);
} else {
// memcpy(data, guest_to_host(addr), len);
size_t offset = (addr - pmem_start);
std::copy(&mem[offset], &mem[offset + len], data);
}
}
bool in_pmem(paddr_t addr) const {
return addr >= pmem_start && addr <= pmem_end;
}
private:
paddr_t pmem_start, pmem_end;
void read_memory(std::filesystem::path filepath, bool is_binary) {
if (!std::filesystem::exists(filepath))
throw std::runtime_error("Memory file not found");
if (is_binary) {
@ -55,30 +71,12 @@ public:
}
}
}
const word_t &operator[](std::size_t addr) { return this->read(addr); }
void transfer(paddr_t addr, uint8_t data[], size_t len, bool is_write) {
if (is_write) {
// memcpy(guest_to_host(addr), data, len);
size_t offset = (addr - pmem_start);
std::copy(data, data + len, &mem[offset]);
} else {
// memcpy(data, guest_to_host(addr), len);
size_t offset = (addr - pmem_start);
std::copy(&mem[offset], &mem[offset + len], data);
}
}
bool in_pmem(paddr_t addr) const {
return addr >= pmem_start && addr <= pmem_end;
}
};
template <typename Mem, typename DevMap> class MemoryMap {
std::unique_ptr<Mem> ram;
std::unique_ptr<DevMap> devices;
std::shared_ptr<spdlog::logger> logger = spdlog::stdout_color_mt("mmap");
public:
MemoryMap(std::unique_ptr<Mem> &&ram, std::unique_ptr<DevMap> &&devices)
MemoryMap(std::unique_ptr<Mem> &&ram,
std::unique_ptr<DevMap> &&devices) noexcept
: ram(std::move(ram)), devices(std::move(devices)) {}
void write(paddr_t waddr, word_t wdata, char wmask) {
@ -122,5 +120,10 @@ public:
void trace(paddr_t addr, bool is_read, word_t pc = 0, word_t value = 0) {
logger->trace("[{}] 0x{:x}", is_read ? 'R' : 'W', this->read(addr));
}
private:
std::unique_ptr<Mem> ram;
std::unique_ptr<DevMap> devices;
std::shared_ptr<spdlog::logger> logger = spdlog::stdout_color_mt("mmap");
};
#endif

View file

@ -12,7 +12,6 @@ struct Config {
bool interactive{false};
bool memory_file_binary = {true};
std::filesystem::path wavefile;
std::filesystem::path lib_ref;
void cli_parse(int argc, char **argv);
};

View file

@ -1,102 +0,0 @@
#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>
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) {
bool enable_disasm = true;
if (n > 30) {
enable_disasm = false;
}
while (n--) {
word_t pc = this->ref.at("pc");
word_t inst = this->ref.at(pc);
if (enable_disasm)
std::cout << d.disassemble(pc, (uint8_t *)&inst, WORD_BYTES)
<< std::endl;
if (inst == 1048691) {
// ebreak
throw TrmRuntimeException(TrmRuntimeException::EBREAK, "ebreak");
}
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 TrmRuntimeException(TrmRuntimeException::DIFFTEST_FAILED,
"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

@ -1,158 +0,0 @@
#ifndef _NPC_TRM_INTERFACE_HEADER_FILE_
#define _NPC_TRM_INTERFACE_HEADER_FILE_
#include <disasm.hpp>
#include <dlfcn.h>
#include <filesystem>
#include <functional>
#include <stdexcept>
#include <string>
#include <types.h>
extern Disassembler d;
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) {
// FIXME: Using this to get pc seems broken
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;
};
class TrmRuntimeException : public std::exception {
private:
const char *msg_;
int code_;
public:
enum { EBREAK, DIFFTEST_FAILED };
TrmRuntimeException(int code, const char *message)
: code_(code), msg_(message) {}
virtual const char *what() const throw() { return msg_; }
int error_code() const { return code_; }
};
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

@ -4,8 +4,7 @@
extern "C" {
#endif
#include <gdbstub.h>
#include <inttypes.h>
#include <stddef.h>
#include <stdint.h>
typedef uint32_t word_t;
typedef int32_t sword_t;
@ -15,10 +14,8 @@ 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;
typedef uint32_t paddr_t;
#define FMT_ADDR "0x%08x"
typedef uint16_t ioaddr_t;
struct Breakpoint {
@ -31,18 +28,8 @@ struct Breakpoint {
#endif
#ifdef __cplusplus
#include <map>
#include <string>
#include <vector>
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}};
struct DbgState {
std::vector<Breakpoint> *bp;
};

View file

@ -23,6 +23,11 @@ class _RegistersVPI : public _RegistersBase<T, nr> {
public:
_RegistersVPI<T, nr>(const std::string regs_prefix,
const std::string pcname) {
init_handlers(regs_prefix, pcname);
}
private:
void init_handlers(const std::string regs_prefix, const std::string pcname) {
for (int i = 0; i < nr; i++) {
std::string regname = regs_prefix + std::to_string(i);
vpiHandle vh = vpi_handle_by_name((PLI_BYTE8 *)regname.c_str(), nullptr);