feat(sdb): support sdb
Some checks failed
Build abstract machine with nix / build-abstract-machine (push) Successful in 2m41s
Run CTests within npc / npc-test (push) Has been cancelled

This commit is contained in:
xinyangli 2024-04-09 17:03:21 +08:00
parent 8500df8a6e
commit e828e140cd
Signed by: xin
SSH key fingerprint: SHA256:qZ/tzd8lYRtUFSrfBDBMcUqV4GHKxqeqRA3huItgvbk
22 changed files with 985 additions and 44 deletions

View file

@ -111,12 +111,17 @@
nixpkgs-circt162.legacyPackages.${system}.circt
yosys
cli11
flex
bison
verilator
];
buildInputs = [
verilator
nvboard
openssl
libllvm
libxml2
readline
] ++ self.checks.${system}.pre-commit-check.enabledPackages;
cmakeFlags = [

View file

@ -34,6 +34,10 @@ if(BUILD_SIM_NVBOARD_TARGET)
find_package(SDL2_image REQUIRED)
endif()
find_package(CLI11 CONFIG REQUIRED)
# TODO: Not required
find_package(LLVM CONFIG REQUIRED)
option(ENABLE_SDB "Enable simple debugger" ON)
find_library(NVBOARD_LIBRARY NAMES nvboard)
find_path(NVBOARD_INCLUDE_DIR NAMES nvboard.h)
@ -59,6 +63,7 @@ endif()
# -- Build Verilator executable and add to test
include_directories(include)
add_subdirectory(utils)
add_subdirectory(csrc)

View file

@ -0,0 +1,49 @@
# Code copied from sethhall@github
#
# - Try to find readline include dirs and libraries
#
# Usage of this module as follows:
#
# find_package(Readline)
#
# Variables used by this module, they can change the default behaviour and need
# to be set before calling find_package:
#
# Readline_ROOT_DIR Set this variable to the root installation of
# readline if the module has problems finding the
# proper installation path.
#
# Variables defined by this module:
#
# READLINE_FOUND System has readline, include and lib dirs found
# Readline_INCLUDE_DIR The readline include directories.
# Readline_LIBRARY The readline library.
find_path(Readline_ROOT_DIR
NAMES include/readline/readline.h
)
find_path(Readline_INCLUDE_DIR
NAMES readline/readline.h
HINTS ${Readline_ROOT_DIR}/include
)
find_library(Readline_LIBRARY
NAMES readline
HINTS ${Readline_ROOT_DIR}/lib
)
if(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
set(READLINE_FOUND TRUE)
else(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
find_library(Readline_LIBRARY NAMES readline)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Readline DEFAULT_MSG Readline_INCLUDE_DIR Readline_LIBRARY )
mark_as_advanced(Readline_INCLUDE_DIR Readline_LIBRARY)
endif(Readline_INCLUDE_DIR AND Readline_LIBRARY AND Ncurses_LIBRARY)
mark_as_advanced(
Readline_ROOT_DIR
Readline_INCLUDE_DIR
Readline_LIBRARY
)

View file

@ -1,5 +1,6 @@
include(ChiselBuild)
add_executable(V${TOPMODULE} config.cpp main.cpp)
target_link_libraries(V${TOPMODULE} PRIVATE disasm sdb)
verilate(V${TOPMODULE} TRACE COVERAGE THREADS
TOP_MODULE ${TOPMODULE}
@ -8,6 +9,7 @@ verilate(V${TOPMODULE} TRACE COVERAGE THREADS
INCLUDE_DIRS ${CHISEL_OUTPUT_DIR}
VERILATOR_ARGS
"--vpi" # Enable VPI
"-Wno-UNOPTFLAT"
)
add_test(

View file

@ -1,10 +1,15 @@
#include "components.hpp"
#include "VFlow___024root.h"
#include "config.hpp"
#include "disasm.hpp"
#include "vl_wrapper.hpp"
#include "vpi_user.h"
#include "vpi_wrapper.hpp"
#include <VFlow.h>
#include <cstdint>
#include <cstdlib>
#include <difftest.hpp>
#include <sdb.hpp>
#include <types.h>
using VlModule = VlModuleInterfaceCommon<VFlow>;
using Registers = _RegistersVPI<uint32_t, 32>;
@ -31,9 +36,10 @@ void pmem_write(int waddr, int wdata, char wmask) {
}
}
Disassembler d{"riscv32-pc-linux-gnu"};
VlModule *top;
Registers *regs;
using CPUState = CPUStateBase<uint32_t, 32>;
vpiHandle pc = nullptr;
void difftest_memcpy(paddr_t, void *, size_t, bool){};
@ -58,6 +64,8 @@ void difftest_exec(uint64_t n) {
}
}
}
// std::cout << d.disassemble(top->rootp->Flow__DOT__pc__DOT__pc_reg, (uint8_t *)&top->rootp->Flow__DOT___ram_inst, 4) << std::endl;
void difftest_init(int port) {
// top = std::make_unique<VlModule>(config.do_trace, config.wavefile);
top = new VlModule{config.do_trace, config.wavefile};
@ -65,20 +73,34 @@ void difftest_init(int port) {
top->reset_eval(10);
}
DifftestInterface dut_interface = DifftestInterface{
&difftest_memcpy, &difftest_regcpy, &difftest_exec, &difftest_init};
SDB::SDB<dut_interface> sdb_dut;
extern "C" {
word_t reg_str2val(const char *name, bool *success) {
return sdb_dut.reg_str2val(name, success);
}
}
int main(int argc, char **argv, char **env) {
config.cli_parse(argc, argv);
/* -- Difftest -- */
std::filesystem::path ref{config.lib_ref};
DifftestInterface dut_interface = DifftestInterface{
&difftest_memcpy, &difftest_regcpy, &difftest_exec, &difftest_init};
DifftestInterface ref_interface = DifftestInterface{ref};
Difftest<CPUStateBase<uint32_t, 32>> diff{dut_interface, ref_interface,
pmem_get(), 128};
int t = 8;
sdb_dut.main_loop();
while (t--) {
diff.step(1);
if (!diff.step(1)) {
uint32_t pc = regs->get_pc();
uint32_t inst = pmem_read(pc);
std::cout << diff << d.disassemble(pc, (uint8_t *)&inst, 4) << std::endl;
return EXIT_FAILURE;
}
}
return 0;

View file

@ -0,0 +1,36 @@
#ifndef _NPC_VPI_WRAPPER_H_
#define _NPC_VPI_WRAPPER_H_
#include <components.hpp>
#include <vpi_user.h>
template <typename T, std::size_t nr>
class _RegistersVPI : public _RegistersBase<T, nr> {
std::array<vpiHandle, nr> reg_handles;
vpiHandle pc_handle;
T vpi_get(vpiHandle vh) {
s_vpi_value v;
v.format = vpiIntVal;
vpi_get_value(vh, &v);
return v.value.integer;
}
T fetch_pc(void) { return vpi_get(pc_handle); }
T fetch_reg(std::size_t id) { return vpi_get(reg_handles[id]); }
public:
_RegistersVPI<T, nr>(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);
if (vh == nullptr) {
std::cerr << "vpiHandle " << regname.c_str() << " not found"
<< std::endl;
exit(EXIT_FAILURE);
}
reg_handles[i] = vh;
}
pc_handle = vpi_handle_by_name((PLI_BYTE8 *)pcname.c_str(), nullptr);
}
};
#endif

View file

@ -1,13 +1,23 @@
#ifndef _NPC_COMPONENTS_H_
#define _NPC_COMPONENTS_H_
#include "vpi_user.h"
#include <array>
#include <cstdlib>
#include <exception>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <verilated_vpi.h>
#include <map>
#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;
@ -25,36 +35,6 @@ public:
}
};
template <typename T, std::size_t nr>
class _RegistersVPI : public _RegistersBase<T, nr> {
std::array<vpiHandle, nr> reg_handles;
vpiHandle pc_handle;
T vpi_get(vpiHandle vh) {
s_vpi_value v;
v.format = vpiIntVal;
vpi_get_value(vh, &v);
return v.value.integer;
}
T fetch_pc(void) { return vpi_get(pc_handle); }
T fetch_reg(std::size_t id) { return vpi_get(reg_handles[id]); }
public:
_RegistersVPI<T, nr>(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);
if (vh == nullptr) {
std::cerr << "vpiHandle " << regname.c_str() << " not found"
<< std::endl;
exit(EXIT_FAILURE);
}
reg_handles[i] = vh;
}
pc_handle = vpi_handle_by_name((PLI_BYTE8 *)pcname.c_str(), nullptr);
}
};
template <typename T, std::size_t n> class Memory {
std::size_t addr_to_index(std::size_t addr) {
if (addr < 0x80000000) {
@ -74,7 +54,8 @@ template <typename T, std::size_t n> class Memory {
public:
std::array<T, n> mem;
Memory(std::filesystem::path filepath, bool is_binary = true) {
assert(std::filesystem::exists(filepath));
if (!std::filesystem::exists(filepath))
throw std::runtime_error("Memory file not found");
if (is_binary) {
std::ifstream file(filepath, std::ios::binary);
char *pmem = reinterpret_cast<char *>(mem.data());

20
npc/include/config.hpp Normal file
View file

@ -0,0 +1,20 @@
#ifndef _NPC_CONFIG_H_
#define _NPC_CONFIG_H_
#include <CLI/App.hpp>
#include <CLI/CLI.hpp>
#include <CLI/Validators.hpp>
#include <filesystem>
struct Config {
std::filesystem::path memory_file;
uint64_t max_sim_time = 1000;
bool memory_file_binary = {true};
bool do_trace{false};
std::filesystem::path wavefile;
std::filesystem::path lib_ref;
void cli_parse(int argc, char **argv);
};
extern Config config;
#endif

View file

@ -1,12 +1,14 @@
#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;
@ -69,14 +71,11 @@ public:
dut.regcpy(dut_state.get(), DIFFTEST_FROM_REF);
}
void step(uint64_t n) {
bool step(uint64_t n) {
ref.exec(n);
dut.exec(n);
fetch_state();
if (*ref_state != *dut_state) {
std::cout << *this;
exit(EXIT_FAILURE);
}
return *ref_state == *dut_state;
}
friend std::ostream &operator<<(std::ostream &os, const Difftest<S> &d) {
@ -90,6 +89,8 @@ public:
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;
@ -106,6 +107,9 @@ template <typename R, size_t nr_reg> struct CPUStateBase {
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>

23
npc/include/types.h Normal file
View file

@ -0,0 +1,23 @@
#ifndef _NPC_TYPES_H__
#define _NPC_TYPES_H__
#include <inttypes.h>
typedef uint32_t word_t;
typedef int32_t sword_t;
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 FMT_WORD "0x%08x"
typedef uint32_t vaddr_t;
typedef uint32_t paddr_t;
#define FMT_ADDR "0x%08x"
typedef uint16_t ioaddr_t;
#ifdef __cplusplus
#include <difftest.hpp>
using CPUState = CPUStateBase<word_t, 32>;
#endif
#endif

View file

@ -0,0 +1,65 @@
#ifndef _NPC_TRACER_H_
#define _NPC_TRACER_H_
#include <filesystem>
#include <verilated_vcd_c.h>
template <class T> class Tracer {
std::shared_ptr<T> top;
std::unique_ptr<VerilatedVcdC> m_trace;
uint64_t cycle = 0;
public:
Tracer(T *top, std::filesystem::path wavefile) {
top = top;
Verilated::traceEverOn(true);
m_trace = std::make_unique<VerilatedVcdC>();
top->trace(m_trace.get(), 5);
m_trace->open(wavefile.c_str());
}
~Tracer() { m_trace->close(); }
/**
* Dump signals to waveform file. Must be called once after every top->eval()
* call.
*/
void update() { m_trace->dump(cycle++); }
};
template <typename T> class VlModuleInterfaceCommon : public T {
uint64_t sim_time = 0;
uint64_t posedge_cnt = 0;
std::unique_ptr<Tracer<T>> tracer;
public:
VlModuleInterfaceCommon<T>(bool do_trace,
std::filesystem::path wavefile = "waveform.vcd") {
if (do_trace)
tracer = std::make_unique<Tracer<T>>(this, wavefile);
}
void eval() {
if (this->is_posedge()) {
posedge_cnt++;
}
T::clock = !T::clock;
sim_time++;
T::eval();
if (tracer)
tracer->update();
}
void eval(int n) {
for (int i = 0; i < n; i++) {
this->eval();
}
}
void reset_eval(int n) {
this->reset = 1;
this->eval(n);
this->reset = 0;
}
bool is_posedge() {
// Will be posedge when eval is called
return T::clock == 0;
}
};
#endif

4
npc/utils/CMakeLists.txt Normal file
View file

@ -0,0 +1,4 @@
add_subdirectory(disasm)
if (ENABLE_SDB)
add_subdirectory(sdb)
endif()

View file

@ -0,0 +1,4 @@
add_library(disasm disasm.cpp)
target_include_directories(disasm PUBLIC include)
llvm_map_components_to_libnames(LLVM_LIBS ${LLVM_TARGETS_TO_BUILD})
target_link_libraries(disasm PUBLIC ${LLVM_LIBS})

View file

@ -0,0 +1,87 @@
#include <disasm.hpp>
#include <llvm/MC/MCAsmInfo.h>
#include <llvm/MC/MCContext.h>
#include <llvm/MC/MCDisassembler/MCDisassembler.h>
#include <llvm/MC/MCInstPrinter.h>
#include <llvm/Support/raw_ostream.h>
#if LLVM_VERSION_MAJOR >= 14
#include <llvm/MC/TargetRegistry.h>
#if LLVM_VERSION_MAJOR >= 15
#include <llvm/MC/MCSubtargetInfo.h>
#endif
#else
#include <llvm/Support/TargetRegistry.h>
#endif
#include <iostream>
#include <llvm/Support/TargetSelect.h>
#include <sstream>
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
#if LLVM_VERSION_MAJOR < 11
#error Please use LLVM with major version >= 11
#endif
Disassembler::Disassembler(std::string triple) : triple(triple) {
llvm::InitializeAllTargetInfos();
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmParsers();
llvm::InitializeAllDisassemblers();
std::string errstr;
llvm::MCInstrInfo *gMII = nullptr;
llvm::MCRegisterInfo *gMRI = nullptr;
auto target = llvm::TargetRegistry::lookupTarget(triple, errstr);
if (!target) {
llvm::errs() << "Can't find target for " << triple << ": " << errstr
<< "\n";
assert(0);
}
llvm::MCTargetOptions MCOptions;
gSTI = target->createMCSubtargetInfo(triple, "", "");
std::string isa = target->getName();
if (isa == "riscv32" || isa == "riscv64") {
gSTI->ApplyFeatureFlag("+m");
gSTI->ApplyFeatureFlag("+a");
gSTI->ApplyFeatureFlag("+c");
gSTI->ApplyFeatureFlag("+f");
gSTI->ApplyFeatureFlag("+d");
}
gMII = target->createMCInstrInfo();
gMRI = target->createMCRegInfo(triple);
auto AsmInfo = target->createMCAsmInfo(*gMRI, triple, MCOptions);
#if LLVM_VERSION_MAJOR >= 13
auto llvmTripleTwine = llvm::Twine(triple);
auto llvmtriple = llvm::Triple(llvmTripleTwine);
auto Ctx = new llvm::MCContext(llvmtriple, AsmInfo, gMRI, nullptr);
#else
auto Ctx = new llvm::MCContext(AsmInfo, gMRI, nullptr);
#endif
gDisassembler = target->createMCDisassembler(*gSTI, *Ctx);
gIP = target->createMCInstPrinter(llvm::Triple(triple),
AsmInfo->getAssemblerDialect(), *AsmInfo,
*gMII, *gMRI);
gIP->setPrintImmHex(true);
gIP->setPrintBranchImmAsAddress(true);
if (isa == "riscv32" || isa == "riscv64")
gIP->applyTargetSpecificCLOption("no-aliases");
}
std::string Disassembler::disassemble(uint64_t pc, uint8_t *code, int nbyte) {
llvm::MCInst inst;
llvm::ArrayRef<uint8_t> arr(code, nbyte);
uint64_t dummy_size = 0;
gDisassembler->getInstruction(inst, dummy_size, arr, pc, llvm::nulls());
std::stringstream ss;
ss << "0x" << std::hex << pc << ": ";
std::string s = ss.str();
llvm::raw_string_ostream os{s};
gIP->printInst(&inst, pc, "", *gSTI, os);
return s;
}

View file

@ -0,0 +1,17 @@
#ifndef _NPC_UTILS_DISASM_
#define _NPC_UTILS_DISASM_
#include "llvm/MC/MCDisassembler/MCDisassembler.h"
#include "llvm/MC/MCInstPrinter.h"
class Disassembler {
llvm::MCDisassembler *gDisassembler = nullptr;
llvm::MCSubtargetInfo *gSTI = nullptr;
llvm::MCInstPrinter *gIP = nullptr;
std::string triple;
public:
Disassembler(std::string);
std::string disassemble(uint64_t pc, uint8_t *code, int nbyte);
};
#endif

View file

@ -0,0 +1,14 @@
find_package(Readline REQUIRED)
find_package(FLEX REQUIRED)
find_package(BISON REQUIRED)
set(PARSER_DIR "${CMAKE_CURRENT_BINARY_DIR}")
set(LEXER_OUT "${PARSER_DIR}/lexer.c")
set(PARSER_OUT "${PARSER_DIR}/parser.c")
flex_target(LEXER addrexp.l "${LEXER_OUT}" DEFINES_FILE "${PARSER_DIR}/addrexp_lex.h")
bison_target(PARSER addrexp.y "${PARSER_OUT}" DEFINES_FILE "${PARSER_DIR}/addrexp.h")
add_flex_bison_dependency(LEXER PARSER)
add_library(sdb sdb.cpp console.cpp "${LEXER_OUT}" "${PARSER_OUT}")
target_link_libraries(sdb PRIVATE ${Readline_LIBRARY})
target_include_directories(sdb PRIVATE ${Readline_INCLUDE_DIR})
target_include_directories(sdb PUBLIC include)

26
npc/utils/sdb/addrexp.l Normal file
View file

@ -0,0 +1,26 @@
%{
#include <types.h>
#include <stdbool.h>
#include "addrexp.h"
static bool success = false;
void yyerror(word_t *result, const char *err);
word_t reg_str2val(const char *name, bool*);
%}
%option noyywrap
%%
0[xX][0-9a-fA-F]+ { yylval = strtoul(yytext, NULL, 16); return HEX_NUMBER; }
[0-9]+ { yylval = strtoul(yytext, NULL, 10); return NUMBER; }
$[asgprt$][0-9pa][0-9]? {
yylval = reg_str2val(yytext + 1, &success);
if(!success) {
yyerror(NULL, "Failed to convert reg to value");
return YYerror;
}
return REGISTER;
}
[+\-*/<=()] { return *yytext; }
[ \t] { }
. { printf("Unexpected character: %s\n", yytext); return YYerror; }
%%

58
npc/utils/sdb/addrexp.y Normal file
View file

@ -0,0 +1,58 @@
%code requires {
#include <types.h>
#include <stdio.h>
#include <stdlib.h>
extern int yylex(void);
}
%{
#include <types.h>
#include <stdio.h>
#include <stdlib.h>
void yyerror(word_t *result, const char *err) {
fprintf(stderr, "%s", err);
}
int pmem_read(int raddr);
%}
%token NUMBER HEX_NUMBER
%token REGISTER
%locations
%start input
%define api.value.type { word_t }
%parse-param { uint32_t *result }
%left '-' '+'
%left '*' '/'
%%
input
: expression { *result = $1; }
;
expression
: number { $$ = $1; }
| expression '>' '=' expression { $$ = ($1 >= $4); }
| expression '<' '=' expression { $$ = ($1 <= $4); }
| expression '=' '=' expression { $$ = ($1 == $4); }
| expression '!' '=' expression { $$ = ($1 == $4); }
| expression '>' expression { $$ = ($1 > $3); }
| expression '<' expression { $$ = ($1 < $3); }
| expression '+' expression { $$ = $1 + $3; }
| expression '-' expression { $$ = $1 - $3; }
| expression '*' expression { $$ = $1 * $3; }
| expression '/' expression {
if($3 == 0) {
fprintf(stderr, "Error: divide by zero at" FMT_WORD " / " FMT_WORD "\n", $1, $3);
YYABORT;
};
$$ = $1 / $3;
}
| '-' number { $$ = -$2; }
| '*' expression { $$ = pmem_read($2); }
| '(' expression ')' { $$ = $2; }
number
: REGISTER
| NUMBER
| HEX_NUMBER
%%

215
npc/utils/sdb/console.cpp Normal file
View file

@ -0,0 +1,215 @@
// cpp-readline library
//
// @author zmij
// @date Nov 30, 2015
#include <console.hpp>
#include <algorithm>
#include <fstream>
#include <functional>
#include <iostream>
#include <iterator>
#include <sstream>
#include <unordered_map>
#include <cstdlib>
#include <cstring>
#include <readline/history.h>
#include <readline/readline.h>
namespace CppReadline {
namespace {
Console *currentConsole = nullptr;
HISTORY_STATE *emptyHistory = history_get_history_state();
} /* namespace */
struct Console::Impl {
using RegisteredCommands =
std::unordered_map<std::string, Console::CommandFunction>;
::std::string greeting_;
// These are hardcoded commands. They do not do anything and are catched manually in the executeCommand function.
RegisteredCommands commands_;
HISTORY_STATE *history_ = nullptr;
Impl(::std::string const &greeting) : greeting_(greeting), commands_() {}
~Impl() { free(history_); }
Impl(Impl const &) = delete;
Impl(Impl &&) = delete;
Impl &operator=(Impl const &) = delete;
Impl &operator=(Impl &&) = delete;
};
// Here we set default commands, they do nothing since we quit with them
// Quitting behaviour is hardcoded in readLine()
Console::Console(std::string const &greeting) : pimpl_{new Impl{greeting}} {
// Init readline basics
rl_attempted_completion_function = &Console::getCommandCompletions;
// These are default hardcoded commands.
// Help command lists available commands.
pimpl_->commands_["help"] = [this](const Arguments &) {
auto commands = getRegisteredCommands();
std::cout << "Available commands are:\n";
for (auto &command : commands)
std::cout << "\t" << command << "\n";
return ReturnCode::Ok;
};
// Run command executes all commands in an external file.
pimpl_->commands_["run"] = [this](const Arguments &input) {
if (input.size() < 2) {
std::cout << "Usage: " << input[0] << " script_filename\n";
return 1;
}
return executeFile(input[1]);
};
// Quit and Exit simply terminate the console.
pimpl_->commands_["quit"] = [this](const Arguments &) {
return ReturnCode::Quit;
};
pimpl_->commands_["exit"] = [this](const Arguments &) {
return ReturnCode::Quit;
};
}
Console::~Console() = default;
void Console::registerCommand(const std::string &s, CommandFunction f) {
pimpl_->commands_[s] = f;
}
std::vector<std::string> Console::getRegisteredCommands() const {
std::vector<std::string> allCommands;
for (auto &pair : pimpl_->commands_)
allCommands.push_back(pair.first);
return allCommands;
}
void Console::saveState() {
free(pimpl_->history_);
pimpl_->history_ = history_get_history_state();
}
void Console::reserveConsole() {
if (currentConsole == this)
return;
// Save state of other Console
if (currentConsole)
currentConsole->saveState();
// Else we swap state
if (!pimpl_->history_)
history_set_history_state(emptyHistory);
else
history_set_history_state(pimpl_->history_);
// Tell others we are using the console
currentConsole = this;
}
void Console::setGreeting(const std::string &greeting) {
pimpl_->greeting_ = greeting;
}
std::string Console::getGreeting() const { return pimpl_->greeting_; }
int Console::executeCommand(const std::string &command) {
// Convert input to vector
std::vector<std::string> inputs;
{
std::istringstream iss(command);
std::copy(std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>(), std::back_inserter(inputs));
}
if (inputs.size() == 0)
return ReturnCode::Ok;
Impl::RegisteredCommands::iterator it;
if ((it = pimpl_->commands_.find(inputs[0])) != end(pimpl_->commands_)) {
return static_cast<int>((it->second)(inputs));
}
std::cout << "Command '" << inputs[0] << "' not found.\n";
return ReturnCode::Error;
}
int Console::executeFile(const std::string &filename) {
std::ifstream input(filename);
if (!input) {
std::cout << "Could not find the specified file to execute.\n";
return ReturnCode::Error;
}
std::string command;
int counter = 0, result;
while (std::getline(input, command)) {
if (command[0] == '#')
continue; // Ignore comments
// Report what the Console is executing.
std::cout << "[" << counter << "] " << command << '\n';
if ((result = executeCommand(command)))
return result;
++counter;
std::cout << '\n';
}
// If we arrived successfully at the end, all is ok
return ReturnCode::Ok;
}
int Console::readLine() {
reserveConsole();
char *buffer = readline(pimpl_->greeting_.c_str());
if (!buffer) {
std::cout
<< '\n'; // EOF doesn't put last endline so we put that so that it looks uniform.
return ReturnCode::Quit;
}
// TODO: Maybe add commands to history only if succeeded?
if (buffer[0] != '\0')
add_history(buffer);
std::string line(buffer);
free(buffer);
return executeCommand(line);
}
char **Console::getCommandCompletions(const char *text, int start, int) {
char **completionList = nullptr;
if (start == 0)
completionList = rl_completion_matches(text, &Console::commandIterator);
return completionList;
}
char *Console::commandIterator(const char *text, int state) {
static Impl::RegisteredCommands::iterator it;
if (!currentConsole)
return nullptr;
auto &commands = currentConsole->pimpl_->commands_;
if (state == 0)
it = begin(commands);
while (it != end(commands)) {
auto &command = it->first;
++it;
if (command.find(text) != std::string::npos) {
return strdup(command.c_str());
}
}
return nullptr;
}
} // namespace CppReadline

View file

@ -0,0 +1,145 @@
// cpp-readline library
//
// @author zmij
// @date Nov 30, 2015
#ifndef CONSOLE_CONSOLE_HEADER_FILE
#define CONSOLE_CONSOLE_HEADER_FILE
#include <functional>
#include <memory>
#include <string>
#include <vector>
namespace CppReadline {
class Console {
public:
/**
* @brief This is the function type that is used to interface with the Console class.
*
* These are the functions that are going to get called by Console
* when the user types in a message. The vector will hold the
* command elements, and the function needs to return its result.
* The result can either be Quit (-1), OK (0), or an arbitrary
* error (>=1).
*/
using Arguments = std::vector<std::string>;
using CommandFunction = std::function<int(const Arguments &)>;
enum ReturnCode {
Quit = -1,
Ok = 0,
Error = 1 // Or greater!
};
/**
* @brief Basic constructor.
*
* The Console comes with two predefined commands: "quit" and
* "exit", which both terminate the console, "help" which prints a
* list of all registered commands, and "run" which executes script
* files.
*
* These commands can be overridden or unregistered - but remember
* to leave at least one to quit ;).
*
* @param greeting This represents the prompt of the Console.
*/
explicit Console(std::string const &greeting);
/**
* @brief Basic destructor.
*
* Frees the history which is been produced by GNU readline.
*/
~Console();
/**
* @brief This function registers a new command within the Console.
*
* If the command already existed, it overwrites the previous entry.
*
* @param s The name of the command as inserted by the user.
* @param f The function that will be called once the user writes the command.
*/
void registerCommand(const std::string &s, CommandFunction f);
/**
* @brief This function returns a list with the currently available commands.
*
* @return A vector containing all registered commands names.
*/
std::vector<std::string> getRegisteredCommands() const;
/**
* @brief Sets the prompt for this Console.
*
* @param greeting The new greeting.
*/
void setGreeting(const std::string &greeting);
/**
* @brief Gets the current prompt of this Console.
*
* @return The currently set greeting.
*/
std::string getGreeting() const;
/**
* @brief This function executes an arbitrary string as if it was inserted via stdin.
*
* @param command The command that needs to be executed.
*
* @return The result of the operation.
*/
int executeCommand(const std::string &command);
/**
* @brief This function calls an external script and executes all commands inside.
*
* This function stops execution as soon as any single command returns something
* different from 0, be it a quit code or an error code.
*
* @param filename The pathname of the script.
*
* @return What the last command executed returned.
*/
int executeFile(const std::string &filename);
/**
* @brief This function executes a single command from the user via stdin.
*
* @return The result of the operation.
*/
int readLine();
private:
Console(const Console &) = delete;
Console(Console &&) = delete;
Console &operator=(Console const &) = delete;
Console &operator=(Console &&) = delete;
struct Impl;
using PImpl = ::std::unique_ptr<Impl>;
PImpl pimpl_;
/**
* @brief This function saves the current state so that some other Console can make use of the GNU readline facilities.
*/
void saveState();
/**
* @brief This function reserves the use of the GNU readline facilities to the calling Console instance.
*/
void reserveConsole();
// GNU newline interface to our commands.
using commandCompleterFunction = char **(const char *text, int start,
int end);
using commandIteratorFunction = char *(const char *text, int state);
static commandCompleterFunction getCommandCompletions;
static commandIteratorFunction commandIterator;
};
} // namespace CppReadline
#endif

View file

@ -0,0 +1,149 @@
#ifndef _SDB_SDB_HEADER_FILE_
#define _SDB_SDB_HEADER_FILE_
#include <components.hpp>
#include <console.hpp>
#include <difftest.hpp>
#include <memory>
#include <stdexcept>
#include <types.h>
namespace cr = CppReadline;
using ret = cr::Console::ReturnCode;
namespace SDB {
enum SDBStatus {
SDB_SUCCESS,
SDB_WRONG_ARGUMENT,
};
struct Handler {
const std::vector<const char *> names;
cr::Console::CommandFunction f;
};
template <const DifftestInterface &funcs> class _SDBHandlers {
using CPUState = CPUStateBase<uint32_t, 32>;
private:
std::vector<Handler> all_handlers;
public:
static CPUState cpu;
private:
static _SDBHandlers<funcs> *instance;
static int cmd_continue(const cr::Console::Arguments &input);
static int cmd_step(const std::vector<std::string> &input);
static int cmd_info_registers(const std::vector<std::string> &input);
static int cmd_print(const std::vector<std::string> &input);
_SDBHandlers<funcs>(std::vector<Handler> all_handlers)
: all_handlers(all_handlers){};
public:
_SDBHandlers<funcs>(const _SDBHandlers<funcs> &) = delete;
_SDBHandlers<funcs> operator=(const _SDBHandlers<funcs> &) = delete;
static _SDBHandlers<funcs> *getInstance() {
if (instance == nullptr) {
std::vector<Handler> all_handlers{
Handler{{"c", "continue"}, &_SDBHandlers::cmd_continue},
Handler{{"si", "step-instruction"}, &_SDBHandlers::cmd_step},
};
instance = new _SDBHandlers<funcs>(all_handlers);
}
return instance;
}
void registerHandlers(cr::Console *c);
};
template <const DifftestInterface &funcs>
_SDBHandlers<funcs> *_SDBHandlers<funcs>::instance = nullptr;
template <const DifftestInterface &funcs>
CPUState _SDBHandlers<funcs>::cpu = CPUState();
template <const DifftestInterface &funcs>
int _SDBHandlers<funcs>::cmd_continue(const cr::Console::Arguments &input) {
if (input.size() > 1)
return SDB_WRONG_ARGUMENT;
funcs.exec(-1);
return SDB_SUCCESS;
}
template <const DifftestInterface &funcs>
int _SDBHandlers<funcs>::cmd_step(const std::vector<std::string> &input) {
if (input.size() > 2) {
return SDB_WRONG_ARGUMENT;
}
uint64_t step_count = input.size() == 2 ? std::stoull(input[1]) : 1;
funcs.exec(step_count);
return SDB_SUCCESS;
}
template <const DifftestInterface &funcs>
int _SDBHandlers<funcs>::cmd_info_registers(
const std::vector<std::string> &input) {
if (input.size() > 1)
return SDB_WRONG_ARGUMENT;
std::cout << _SDBHandlers<funcs>::getInstance()->cpu << std::endl;
return SDB_SUCCESS;
}
template <const DifftestInterface &funcs>
int _SDBHandlers<funcs>::cmd_print(const std::vector<std::string> &input) {
exit(1);
}
template <const DifftestInterface &funcs>
void _SDBHandlers<funcs>::registerHandlers(cr::Console *c) {
for (auto &h : this->all_handlers) {
for (auto &name : h.names) {
c->registerCommand(name, h.f);
}
}
}
template <const DifftestInterface &funcs> class SDB {
private:
std::unique_ptr<CppReadline::Console> c;
using SDBHandlers = _SDBHandlers<funcs>;
public:
SDB(std::string const &greeting = "\033[1;34m(npc)\033[0m ") {
c = std::make_unique<CppReadline::Console>(greeting);
SDBHandlers::getInstance()->registerHandlers(c.get());
};
word_t reg_str2val(const char *name, bool *success) {
try {
*success = true;
return SDBHandlers::getInstance()->cpu.at(name);
} catch (std::runtime_error) {
*success = false;
return 0;
}
}
int main_loop() {
int retCode;
do {
retCode = c->readLine();
// We can also change the prompt based on last return value:
if (retCode == ret::Ok)
c->setGreeting("\033[1;34m(npc)\033[0m ");
else
c->setGreeting("\033[1;31m(npc)\033[0m ");
if (retCode == SDB_WRONG_ARGUMENT) {
std::cout << "Wrong argument give to command\n";
}
} while (retCode != ret::Quit);
return 0;
}
};
} // namespace SDB
#endif

10
npc/utils/sdb/sdb.cpp Normal file
View file

@ -0,0 +1,10 @@
#include <components.hpp>
#include <console.hpp>
#include <difftest.hpp>
#include <sdb.hpp>
#include <types.h>
namespace cr = CppReadline;
using ret = cr::Console::ReturnCode;
namespace SDB {}