diff --git a/flake.nix b/flake.nix index b2e8142..c824872 100644 --- a/flake.nix +++ b/flake.nix @@ -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 = [ diff --git a/npc/CMakeLists.txt b/npc/CMakeLists.txt index 78dbfe5..bcd356b 100644 --- a/npc/CMakeLists.txt +++ b/npc/CMakeLists.txt @@ -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) diff --git a/npc/cmake/FindReadline.cmake b/npc/cmake/FindReadline.cmake new file mode 100644 index 0000000..dc2dd41 --- /dev/null +++ b/npc/cmake/FindReadline.cmake @@ -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 +) \ No newline at end of file diff --git a/npc/csrc/Flow/CMakeLists.txt b/npc/csrc/Flow/CMakeLists.txt index 606fd73..ad8d2e9 100644 --- a/npc/csrc/Flow/CMakeLists.txt +++ b/npc/csrc/Flow/CMakeLists.txt @@ -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( diff --git a/npc/csrc/Flow/main.cpp b/npc/csrc/Flow/main.cpp index 50eeeeb..b1c37c9 100644 --- a/npc/csrc/Flow/main.cpp +++ b/npc/csrc/Flow/main.cpp @@ -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 #include +#include #include +#include +#include using VlModule = VlModuleInterfaceCommon; using Registers = _RegistersVPI; @@ -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; 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(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 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> 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; diff --git a/npc/csrc/Flow/vpi_wrapper.hpp b/npc/csrc/Flow/vpi_wrapper.hpp new file mode 100644 index 0000000..a1dd089 --- /dev/null +++ b/npc/csrc/Flow/vpi_wrapper.hpp @@ -0,0 +1,36 @@ +#ifndef _NPC_VPI_WRAPPER_H_ +#define _NPC_VPI_WRAPPER_H_ +#include +#include + +template +class _RegistersVPI : public _RegistersBase { + std::array 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(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 diff --git a/npc/csrc/Flow/components.hpp b/npc/include/components.hpp similarity index 66% rename from npc/csrc/Flow/components.hpp rename to npc/include/components.hpp index a84309a..fced1af 100644 --- a/npc/csrc/Flow/components.hpp +++ b/npc/include/components.hpp @@ -1,13 +1,23 @@ #ifndef _NPC_COMPONENTS_H_ #define _NPC_COMPONENTS_H_ -#include "vpi_user.h" #include #include +#include #include #include #include -#include +#include +#include +#include + +const std::map 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 class _RegistersBase { std::array regs; @@ -25,36 +35,6 @@ public: } }; -template -class _RegistersVPI : public _RegistersBase { - std::array 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(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 class Memory { std::size_t addr_to_index(std::size_t addr) { if (addr < 0x80000000) { @@ -74,7 +54,8 @@ template class Memory { public: std::array 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(mem.data()); diff --git a/npc/include/config.hpp b/npc/include/config.hpp new file mode 100644 index 0000000..9f5873c --- /dev/null +++ b/npc/include/config.hpp @@ -0,0 +1,20 @@ +#ifndef _NPC_CONFIG_H_ +#define _NPC_CONFIG_H_ +#include +#include +#include +#include + +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 \ No newline at end of file diff --git a/npc/include/difftest.hpp b/npc/include/difftest.hpp index dd690b9..7349004 100644 --- a/npc/include/difftest.hpp +++ b/npc/include/difftest.hpp @@ -1,12 +1,14 @@ #ifndef _DIFFTEST_DIFFTEST_H_ #define _DIFFTEST_DIFFTEST_H_ #include +#include #include #include #include #include #include #include +#include #include 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 &d) { @@ -90,6 +89,8 @@ public: template struct CPUStateBase { R reg[nr_reg] = {0}; paddr_t pc = 0x80000000; + static const std::map 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 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 diff --git a/npc/include/types.h b/npc/include/types.h new file mode 100644 index 0000000..f6a8d87 --- /dev/null +++ b/npc/include/types.h @@ -0,0 +1,23 @@ +#ifndef _NPC_TYPES_H__ +#define _NPC_TYPES_H__ +#include + +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 +using CPUState = CPUStateBase; +#endif + +#endif \ No newline at end of file diff --git a/npc/include/vl_wrapper.hpp b/npc/include/vl_wrapper.hpp new file mode 100644 index 0000000..4d42b55 --- /dev/null +++ b/npc/include/vl_wrapper.hpp @@ -0,0 +1,65 @@ +#ifndef _NPC_TRACER_H_ +#define _NPC_TRACER_H_ +#include +#include + +template class Tracer { + std::shared_ptr top; + std::unique_ptr m_trace; + uint64_t cycle = 0; + +public: + Tracer(T *top, std::filesystem::path wavefile) { + top = top; + Verilated::traceEverOn(true); + m_trace = std::make_unique(); + 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 class VlModuleInterfaceCommon : public T { + uint64_t sim_time = 0; + uint64_t posedge_cnt = 0; + std::unique_ptr> tracer; + +public: + VlModuleInterfaceCommon(bool do_trace, + std::filesystem::path wavefile = "waveform.vcd") { + if (do_trace) + tracer = std::make_unique>(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 diff --git a/npc/utils/CMakeLists.txt b/npc/utils/CMakeLists.txt new file mode 100644 index 0000000..017313c --- /dev/null +++ b/npc/utils/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(disasm) +if (ENABLE_SDB) + add_subdirectory(sdb) +endif() diff --git a/npc/utils/disasm/CMakeLists.txt b/npc/utils/disasm/CMakeLists.txt new file mode 100644 index 0000000..e019bf7 --- /dev/null +++ b/npc/utils/disasm/CMakeLists.txt @@ -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}) diff --git a/npc/utils/disasm/disasm.cpp b/npc/utils/disasm/disasm.cpp new file mode 100644 index 0000000..0e817ba --- /dev/null +++ b/npc/utils/disasm/disasm.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#if LLVM_VERSION_MAJOR >= 14 +#include +#if LLVM_VERSION_MAJOR >= 15 +#include +#endif +#else +#include +#endif +#include +#include +#include + +#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 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; +} diff --git a/npc/utils/disasm/include/disasm.hpp b/npc/utils/disasm/include/disasm.hpp new file mode 100644 index 0000000..a14ebb3 --- /dev/null +++ b/npc/utils/disasm/include/disasm.hpp @@ -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 \ No newline at end of file diff --git a/npc/utils/sdb/CMakeLists.txt b/npc/utils/sdb/CMakeLists.txt new file mode 100644 index 0000000..6b1cb2c --- /dev/null +++ b/npc/utils/sdb/CMakeLists.txt @@ -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) diff --git a/npc/utils/sdb/addrexp.l b/npc/utils/sdb/addrexp.l new file mode 100644 index 0000000..d81f0e1 --- /dev/null +++ b/npc/utils/sdb/addrexp.l @@ -0,0 +1,26 @@ +%{ + #include + #include + #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; } +%% diff --git a/npc/utils/sdb/addrexp.y b/npc/utils/sdb/addrexp.y new file mode 100644 index 0000000..ca1a5c9 --- /dev/null +++ b/npc/utils/sdb/addrexp.y @@ -0,0 +1,58 @@ +%code requires { + #include + #include + #include + extern int yylex(void); +} +%{ + #include + #include + #include + 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 + +%% diff --git a/npc/utils/sdb/console.cpp b/npc/utils/sdb/console.cpp new file mode 100644 index 0000000..485e192 --- /dev/null +++ b/npc/utils/sdb/console.cpp @@ -0,0 +1,215 @@ +// cpp-readline library +// +// @author zmij +// @date Nov 30, 2015 + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace CppReadline { +namespace { + +Console *currentConsole = nullptr; +HISTORY_STATE *emptyHistory = history_get_history_state(); + +} /* namespace */ + +struct Console::Impl { + using RegisteredCommands = + std::unordered_map; + + ::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 Console::getRegisteredCommands() const { + std::vector 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 inputs; + { + std::istringstream iss(command); + std::copy(std::istream_iterator(iss), + std::istream_iterator(), 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((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 diff --git a/npc/utils/sdb/include/console.hpp b/npc/utils/sdb/include/console.hpp new file mode 100644 index 0000000..538ce2f --- /dev/null +++ b/npc/utils/sdb/include/console.hpp @@ -0,0 +1,145 @@ +// cpp-readline library +// +// @author zmij +// @date Nov 30, 2015 + +#ifndef CONSOLE_CONSOLE_HEADER_FILE +#define CONSOLE_CONSOLE_HEADER_FILE + +#include +#include +#include +#include + +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; + using CommandFunction = std::function; + + 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 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; + 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 diff --git a/npc/utils/sdb/include/sdb.hpp b/npc/utils/sdb/include/sdb.hpp new file mode 100644 index 0000000..18aae8a --- /dev/null +++ b/npc/utils/sdb/include/sdb.hpp @@ -0,0 +1,149 @@ +#ifndef _SDB_SDB_HEADER_FILE_ +#define _SDB_SDB_HEADER_FILE_ + +#include +#include +#include +#include +#include +#include + +namespace cr = CppReadline; +using ret = cr::Console::ReturnCode; + +namespace SDB { + +enum SDBStatus { + SDB_SUCCESS, + SDB_WRONG_ARGUMENT, +}; + +struct Handler { + const std::vector names; + cr::Console::CommandFunction f; +}; + +template class _SDBHandlers { + using CPUState = CPUStateBase; + +private: + std::vector all_handlers; + +public: + static CPUState cpu; + +private: + static _SDBHandlers *instance; + static int cmd_continue(const cr::Console::Arguments &input); + static int cmd_step(const std::vector &input); + static int cmd_info_registers(const std::vector &input); + static int cmd_print(const std::vector &input); + _SDBHandlers(std::vector all_handlers) + : all_handlers(all_handlers){}; + +public: + _SDBHandlers(const _SDBHandlers &) = delete; + _SDBHandlers operator=(const _SDBHandlers &) = delete; + + static _SDBHandlers *getInstance() { + if (instance == nullptr) { + std::vector all_handlers{ + Handler{{"c", "continue"}, &_SDBHandlers::cmd_continue}, + Handler{{"si", "step-instruction"}, &_SDBHandlers::cmd_step}, + }; + instance = new _SDBHandlers(all_handlers); + } + return instance; + } + + void registerHandlers(cr::Console *c); +}; + +template +_SDBHandlers *_SDBHandlers::instance = nullptr; + +template +CPUState _SDBHandlers::cpu = CPUState(); + +template +int _SDBHandlers::cmd_continue(const cr::Console::Arguments &input) { + if (input.size() > 1) + return SDB_WRONG_ARGUMENT; + funcs.exec(-1); + return SDB_SUCCESS; +} + +template +int _SDBHandlers::cmd_step(const std::vector &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 +int _SDBHandlers::cmd_info_registers( + const std::vector &input) { + if (input.size() > 1) + return SDB_WRONG_ARGUMENT; + std::cout << _SDBHandlers::getInstance()->cpu << std::endl; + return SDB_SUCCESS; +} + +template +int _SDBHandlers::cmd_print(const std::vector &input) { + exit(1); +} + +template +void _SDBHandlers::registerHandlers(cr::Console *c) { + for (auto &h : this->all_handlers) { + for (auto &name : h.names) { + c->registerCommand(name, h.f); + } + } +} + +template class SDB { +private: + std::unique_ptr c; + using SDBHandlers = _SDBHandlers; + +public: + SDB(std::string const &greeting = "\033[1;34m(npc)\033[0m ") { + c = std::make_unique(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 \ No newline at end of file diff --git a/npc/utils/sdb/sdb.cpp b/npc/utils/sdb/sdb.cpp new file mode 100644 index 0000000..9bfa6b5 --- /dev/null +++ b/npc/utils/sdb/sdb.cpp @@ -0,0 +1,10 @@ +#include +#include +#include +#include +#include + +namespace cr = CppReadline; +using ret = cr::Console::ReturnCode; + +namespace SDB {}