diff --git a/dv/verilator/simutil_verilator/README.md b/dv/verilator/memutil/README.md similarity index 94% rename from dv/verilator/simutil_verilator/README.md rename to dv/verilator/memutil/README.md index 5b6347a2..f052b2f3 100644 --- a/dv/verilator/simutil_verilator/README.md +++ b/dv/verilator/memutil/README.md @@ -1,4 +1,4 @@ -# Verilator simulator support +# Verilator memory loading support ## ELF support diff --git a/dv/verilator/memutil/cpp/verilator_memutil.cc b/dv/verilator/memutil/cpp/verilator_memutil.cc new file mode 100644 index 00000000..69e2f6b3 --- /dev/null +++ b/dv/verilator/memutil/cpp/verilator_memutil.cc @@ -0,0 +1,433 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "verilator_memutil.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// DPI Exports +extern "C" { + +/** + * Write |file| to a memory + * + * @param file path to a SystemVerilog $readmemh()-compatible file (VMEM file) + */ +extern void simutil_verilator_memload(const char *file); + +/** + * Write a 32 bit word |val| to memory at index |index| + * + * @return 1 if successful, 0 otherwise + */ +extern int simutil_verilator_set_mem(int index, const svLogicVecVal *val); +} + +struct BufferDesc { + uint8_t *data; + size_t length; +}; + +bool VerilatorMemUtil::RegisterMemoryArea(const std::string name, + const std::string location) { + MemArea mem = {.name = name, .location = location}; + + auto ret = mem_register_.emplace(name, mem); + if (ret.second == false) { + std::cerr << "ERROR: Can not register \"" << name << "\" at: \"" << location + << "\" (Previously defined at: \"" << ret.first->second.location + << "\")" << std::endl; + return false; + } + return true; +} + +bool VerilatorMemUtil::ParseCLIArguments(int argc, char **argv, + bool &exit_app) { + const struct option long_options[] = { + {"rominit", required_argument, nullptr, 'r'}, + {"raminit", required_argument, nullptr, 'm'}, + {"flashinit", required_argument, nullptr, 'f'}, + {"meminit", required_argument, nullptr, 'l'}, + {"help", no_argument, nullptr, 'h'}, + {nullptr, no_argument, nullptr, 0}}; + + // Reset the command parsing index in-case other utils have already parsed + // some arguments + optind = 1; + while (1) { + int c = getopt_long(argc, argv, ":r:m:f:l:h", long_options, nullptr); + if (c == -1) { + break; + } + + // Disable error reporting by getopt + opterr = 0; + + switch (c) { + case 0: + break; + case 'r': + if (!MemWrite("rom", optarg)) { + std::cerr << "ERROR: Unable to initialize memory." << std::endl; + return false; + } + break; + case 'm': + if (!MemWrite("ram", optarg)) { + std::cerr << "ERROR: Unable to initialize memory." << std::endl; + return false; + } + break; + case 'f': + if (!MemWrite("flash", optarg)) { + std::cerr << "ERROR: Unable to initialize memory." << std::endl; + return false; + } + break; + case 'l': { + if (strcasecmp(optarg, "list") == 0) { + PrintMemRegions(); + exit_app = true; + return true; + } + + std::string name; + std::string filepath; + MemImageType type; + if (!ParseMemArg(optarg, name, filepath, type)) { + std::cerr << "ERROR: Unable to parse meminit arguments." << std::endl; + return false; + } + + if (!MemWrite(name, filepath, type)) { + std::cerr << "ERROR: Unable to initialize memory." << std::endl; + return false; + } + } break; + case 'h': + PrintHelp(); + return true; + case ':': // missing argument + std::cerr << "ERROR: Missing argument." << std::endl << std::endl; + return false; + case '?': + default:; + // Ignore unrecognized options since they might be consumed by + // other utils + } + } + + return true; +} + +void VerilatorMemUtil::PrintMemRegions() const { + std::cout << "Registered memory regions:" << std::endl; + for (const auto &m : mem_register_) { + std::cout << "\t'" << m.second.name << "' at location: '" + << m.second.location << "'" << std::endl; + } +} + +void VerilatorMemUtil::PrintHelp() const { + std::cout << "Simulation memory utilities:\n\n" + "-r|--rominit=FILE\n" + " Initialize the ROM with FILE (elf/vmem)\n\n" + "-m|--raminit=FILE\n" + " Initialize the RAM with FILE (elf/vmem)\n\n" + "-f|--flashinit=FILE\n" + " Initialize the FLASH with FILE (elf/vmem)\n\n" + "-l|--meminit=NAME,FILE[,TYPE]\n" + " Initialize memory region NAME with FILE [of TYPE]\n" + " TYPE is either 'elf' or 'vmem'\n\n" + "-l list|--meminit=list\n" + " Print registered memory regions\n\n" + "-h|--help\n" + " Show help\n\n"; +} + +bool VerilatorMemUtil::ParseMemArg(std::string mem_argument, std::string &name, + std::string &filepath, MemImageType &type) { + std::array args; + size_t pos = 0; + size_t end_pos = 0; + size_t i; + + for (i = 0; i < 3; ++i) { + end_pos = mem_argument.find(",", pos); + // Check for possible exit conditions + if (pos == end_pos) { + std::cerr << "ERROR: empty field in: " << mem_argument << std::endl; + return false; + } + if (end_pos == std::string::npos) { + args[i] = mem_argument.substr(pos); + break; + } + args[i] = mem_argument.substr(pos, end_pos - pos); + pos = end_pos + 1; + } + // mem_argument is not empty as getopt requires an argument, + // but not a valid argument for memory initialization + if (i == 0) { + std::cerr << "ERROR: meminit must be in \"name,file[,type]\"" + << " got: " << mem_argument << std::endl; + return false; + } + + name = args[0]; + filepath = args[1]; + + if (i == 1) { + // Type not set explicitly + type = DetectMemImageType(filepath); + } else { + type = GetMemImageTypeByName(args[2]); + } + + return true; +} + +MemImageType VerilatorMemUtil::DetectMemImageType(const std::string filepath) { + size_t ext_pos = filepath.find_last_of("."); + std::string ext = filepath.substr(ext_pos + 1); + + if (ext_pos == std::string::npos) { + // Assume ELF files if no file extension is given. + // TODO: Make this more robust by actually checking the file contents. + return kMemImageElf; + } + return GetMemImageTypeByName(ext); +} + +MemImageType VerilatorMemUtil::GetMemImageTypeByName(const std::string name) { + if (name.compare("elf") == 0) { + return kMemImageElf; + } + if (name.compare("vmem") == 0) { + return kMemImageVmem; + } + return kMemImageUnknown; +} + +bool VerilatorMemUtil::IsFileReadable(std::string filepath) const { + struct stat statbuf; + return stat(filepath.data(), &statbuf) == 0; +} + +bool VerilatorMemUtil::ElfFileToBinary(const std::string &filepath, + uint8_t **data, + size_t &len_bytes) const { + bool retval; + std::list buffers; + size_t offset = 0; + (void)elf_errno(); + len_bytes = 0; + + if (elf_version(EV_CURRENT) == EV_NONE) { + std::cerr << elf_errmsg(-1) << std::endl; + return false; + } + + int fd = open(filepath.c_str(), O_RDONLY, 0); + if (fd < 0) { + std::cerr << "Could not open file: " << filepath << std::endl; + return false; + } + + Elf *elf_desc; + elf_desc = elf_begin(fd, ELF_C_READ, NULL); + if (elf_desc == NULL) { + std::cerr << elf_errmsg(-1) << " in: " << filepath << std::endl; + retval = false; + goto return_fd_end; + } + if (elf_kind(elf_desc) != ELF_K_ELF) { + std::cerr << "Not a ELF file: " << filepath << std::endl; + retval = false; + goto return_elf_end; + } + // TODO: add support for ELFCLASS64 + if (gelf_getclass(elf_desc) != ELFCLASS32) { + std::cerr << "Not a 32-bit ELF file: " << filepath << std::endl; + retval = false; + goto return_elf_end; + } + + size_t phnum; + if (elf_getphdrnum(elf_desc, &phnum) != 0) { + std::cerr << elf_errmsg(-1) << " in: " << filepath << std::endl; + retval = false; + goto return_elf_end; + } + + GElf_Phdr phdr; + Elf_Data *elf_data; + elf_data = NULL; + for (size_t i = 0; i < phnum; i++) { + if (gelf_getphdr(elf_desc, i, &phdr) == NULL) { + std::cerr << elf_errmsg(-1) << " segment number: " << i + << " in: " << filepath << std::endl; + retval = false; + goto return_elf_end; + } + if (phdr.p_type != PT_LOAD) { + std::cout << "Program header number " << i << "is not of type PT_LOAD." + << "Continue." << std::endl; + continue; + } + elf_data = elf_getdata_rawchunk(elf_desc, phdr.p_offset, phdr.p_filesz, + ELF_T_BYTE); + + if (elf_data == NULL) { + retval = false; + goto return_elf_end; + } + + BufferDesc buf_data; + buf_data.length = elf_data->d_size; + len_bytes += buf_data.length; + buf_data.data = (uint8_t *)malloc(elf_data->d_size); + memcpy(buf_data.data, ((uint8_t *)elf_data->d_buf), buf_data.length); + buffers.push_back(buf_data); + } + + // Put the collected data into a continuous buffer + // Memory is freed by the caller + *data = (uint8_t *)malloc(len_bytes); + for (std::list::iterator it = buffers.begin(); + it != buffers.end(); ++it) { + memcpy(((uint8_t *)*data) + offset, it->data, it->length); + offset += it->length; + free(it->data); + } + buffers.clear(); + + retval = true; + +return_elf_end: + elf_end(elf_desc); +return_fd_end: + close(fd); + return retval; +} + +bool VerilatorMemUtil::MemWrite(const std::string &name, + const std::string &filepath) { + MemImageType type = DetectMemImageType(filepath); + if (type == kMemImageUnknown) { + std::cerr << "ERROR: Unable to detect file type for: " << filepath + << std::endl; + // Continuing for more error messages + } + return MemWrite(name, filepath, type); +} + +bool VerilatorMemUtil::MemWrite(const std::string &name, + const std::string &filepath, + MemImageType type) { + // Search for corresponding registered memory based on the name + auto it = mem_register_.find(name); + if (it == mem_register_.end()) { + std::cerr << "ERROR: Memory location not set for: '" << name << "'" + << std::endl; + PrintMemRegions(); + return false; + } + + if (!MemWrite(it->second, filepath, type)) { + std::cerr << "ERROR: Setting memory '" << name << "' failed." << std::endl; + return false; + } + return true; +} + +bool VerilatorMemUtil::MemWrite(const MemArea &m, const std::string &filepath, + MemImageType type) { + if (!IsFileReadable(filepath)) { + std::cerr << "ERROR: Memory initialization file " + << "'" << filepath << "'" + << " is not readable." << std::endl; + return false; + } + + svScope scope = svGetScopeFromName(m.location.data()); + if (!scope) { + std::cerr << "ERROR: No memory found at " << m.location << std::endl; + return false; + } + + switch (type) { + case kMemImageElf: + if (!WriteElfToMem(scope, filepath)) { + std::cerr << "ERROR: Writing ELF file to memory \"" << m.name << "\" (" + << m.location << ") failed." << std::endl; + return false; + } + break; + case kMemImageVmem: + if (!WriteVmemToMem(scope, filepath)) { + std::cerr << "ERROR: Writing VMEM file to memory \"" << m.name << "\" (" + << m.location << ") failed." << std::endl; + return false; + } + break; + case kMemImageUnknown: + default: + std::cerr << "ERROR: Unknown file type for " << m.name << std::endl; + return false; + } + return true; +} + +bool VerilatorMemUtil::WriteElfToMem(const svScope &scope, + const std::string &filepath) { + bool retcode; + svScope prev_scope = svSetScope(scope); + + uint8_t *buf = nullptr; + size_t len_bytes; + + if (!ElfFileToBinary(filepath, &buf, len_bytes)) { + std::cerr << "ERROR: Could not load: " << filepath << std::endl; + retcode = false; + goto ret; + } + for (int i = 0; i < len_bytes / 4; ++i) { + if (!simutil_verilator_set_mem(i, (svLogicVecVal *)&buf[4 * i])) { + std::cerr << "ERROR: Could not set memory byte: " << i * 4 << "/" + << len_bytes << "" << std::endl; + + retcode = false; + goto ret; + } + } + + retcode = true; + +ret: + svSetScope(prev_scope); + free(buf); + return retcode; +} + +bool VerilatorMemUtil::WriteVmemToMem(const svScope &scope, + const std::string &filepath) { + svScope prev_scope = svSetScope(scope); + + // TODO: Add error handling. + simutil_verilator_memload(filepath.data()); + + svSetScope(prev_scope); + return true; +} diff --git a/dv/verilator/memutil/cpp/verilator_memutil.h b/dv/verilator/memutil/cpp/verilator_memutil.h new file mode 100644 index 00000000..edd03ff3 --- /dev/null +++ b/dv/verilator/memutil/cpp/verilator_memutil.h @@ -0,0 +1,105 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef VERILATOR_MEMUTIL_H_ +#define VERILATOR_MEMUTIL_H_ + +#include "sim_ctrl_extension.h" + +#include + +#include +#include + +enum MemImageType { + kMemImageUnknown = 0, + kMemImageElf, + kMemImageVmem, +}; + +struct MemArea { + std::string name; // Unique identifier + std::string location; // Design scope location +}; + +/** + * Provide various memory loading utilities for Verilator simulations + * + * These utilities require the corresponding DPI functions: + * simutil_verilator_memload() + * simutil_verilator_set_mem() + * to be defined somewhere as SystemVerilog functions. + */ +class VerilatorMemUtil : public SimCtrlExtension { + public: + /** + * Register a memory as instantiated by generic ram + * + * The |name| must be a unique identifier. The function will return false + * if |name| is already used. |location| is the path to the scope of the + * instantiated memory, which needs to support the DPI-C interfaces + * 'simutil_verilator_memload' and 'simutil_verilator_set_mem' used for + * 'vmem' and 'elf' files, respectively. + * + * Memories must be registered before command arguments are parsed by + * ParseCommandArgs() in order for them to be known. + */ + bool RegisterMemoryArea(const std::string name, const std::string location); + + /** + * Parse command line arguments + * + * Process all recognized command-line arguments from argc/argv. + * + * @param argc, argv Standard C command line arguments + * @param exit_app Indicate that program should terminate + * @return Return code, true == success + */ + virtual bool ParseCLIArguments(int argc, char **argv, bool &exit_app); + + private: + std::map mem_register_; + + /** + * Print a list of all registered memory regions + * + * @see RegisterMemoryArea() + */ + void PrintMemRegions() const; + + /** + * Print help how to use this tool + */ + void PrintHelp() const; + + /** + * Parse argument section specific to memory initialization. + * + * Must be in the form of: name,file[,type]. + */ + bool ParseMemArg(std::string mem_argument, std::string &name, + std::string &filepath, MemImageType &type); + + MemImageType DetectMemImageType(const std::string filepath); + + MemImageType GetMemImageTypeByName(const std::string name); + + bool IsFileReadable(std::string filepath) const; + + /** + * Dump an ELF file into a raw binary + */ + bool ElfFileToBinary(const std::string &filepath, uint8_t **data, + size_t &len_bytes) const; + + bool MemWrite(const std::string &name, const std::string &filepath); + bool MemWrite(const std::string &name, const std::string &filepath, + MemImageType type); + bool MemWrite(const MemArea &m, const std::string &filepath, + MemImageType type); + bool WriteElfToMem(const svScope &scope, const std::string &filepath); + bool WriteVmemToMem(const svScope &scope, const std::string &filepath); +}; + +#endif // VERILATOR_MEMUTIL_H_ diff --git a/dv/verilator/memutil/memutil_verilator.core b/dv/verilator/memutil/memutil_verilator.core new file mode 100644 index 00000000..2cd1831b --- /dev/null +++ b/dv/verilator/memutil/memutil_verilator.core @@ -0,0 +1,20 @@ +CAPI=2: +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +name: "lowrisc:dv_verilator:memutil_verilator" +description: "Verilator memory utilities" +filesets: + files_cpp: + depend: + - lowrisc:dv_verilator:simutil_verilator + files: + - cpp/verilator_memutil.cc + - cpp/verilator_memutil.h: { is_include_file: true } + file_type: cppSource + +targets: + default: + filesets: + - files_cpp diff --git a/dv/verilator/simutil_verilator/cpp/sim_ctrl_extension.h b/dv/verilator/simutil_verilator/cpp/sim_ctrl_extension.h new file mode 100644 index 00000000..20d4353d --- /dev/null +++ b/dv/verilator/simutil_verilator/cpp/sim_ctrl_extension.h @@ -0,0 +1,39 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef SIM_CTRL_EXTENSION_H_ +#define SIM_CTRL_EXTENSION_H_ + +class SimCtrlExtension { + public: + /** + * Parse command line arguments + * + * Process all recognized command-line arguments from argc/argv. + * + * @param argc, argv Standard C command line arguments + * @param exit_app Indicate that program should terminate + * @return Return code, true == success + */ + virtual bool ParseCLIArguments(int argc, char **argv, bool &exit_app) { + return true; + } + + /** + * Function to be called prior to executing the simulation + */ + virtual void PreExec() {} + + /** + * Function to be called every clock cycle + */ + virtual void OnClock(unsigned long sim_time) {} + + /** + * Function to be called after executing the simulation + */ + virtual void PostExec() {} +}; + +#endif // SIM_CTRL_EXTENSION_H_ diff --git a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc index a460b0e3..28c2a925 100644 --- a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc +++ b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc @@ -4,79 +4,32 @@ #include "verilator_sim_ctrl.h" -#include -#include #include -#include #include #include -#include -#include +#include #include -#include -#include // This is defined by Verilator and passed through the command line #ifndef VM_TRACE #define VM_TRACE 0 #endif -struct BufferDesc { - uint8_t *data; - size_t length; -}; - /** * Get the current simulation time * * Called by $time in Verilog, converts to double, to match what SystemC does */ -double sc_time_stamp() { - return VerilatorSimCtrl::GetInstance().GetTime(); -} +double sc_time_stamp() { return VerilatorSimCtrl::GetInstance().GetTime(); } -// DPI Exports -extern "C" { - -/** - * Write |file| to a memory - * - * @param file path to a SystemVerilog $readmemh()-compatible file (VMEM file) - */ -extern void simutil_verilator_memload(const char *file); - -/** - * Write a 32 bit word |val| to memory at index |index| - * - * @return 1 if successful, 0 otherwise - */ -extern int simutil_verilator_set_mem(int index, const svLogicVecVal *val); -} - -VerilatorSimCtrl& VerilatorSimCtrl::GetInstance() { +VerilatorSimCtrl &VerilatorSimCtrl::GetInstance() { static VerilatorSimCtrl instance; return instance; } -VerilatorSimCtrl::VerilatorSimCtrl() - : top_(nullptr), - time_(0), - tracing_enabled_(false), - tracing_enabled_changed_(false), - tracing_ever_enabled_(false), - tracing_possible_(VM_TRACE), - initial_reset_delay_cycles_(2), - reset_duration_cycles_(2), - request_stop_(false), - simulation_success_(true), - tracer_(VerilatedTracer()), - term_after_cycles_(0), - callback_(nullptr) {} - void VerilatorSimCtrl::SetTop(VerilatedToplevel *top, CData *sig_clk, - CData *sig_rst, - VerilatorSimCtrlFlags flags) { + CData *sig_rst, VerilatorSimCtrlFlags flags) { top_ = top; sig_clk_ = sig_clk; sig_rst_ = sig_rst; @@ -110,8 +63,16 @@ void VerilatorSimCtrl::RunSimulation() { << std::endl << "$ kill -USR1 " << getpid() << std::endl; } + // Call all extension pre-exec methods + for (auto it = extension_array_.begin(); it != extension_array_.end(); ++it) { + (*it)->PreExec(); + } // Run the simulation Run(); + // Call all extension post-exec methods + for (auto it = extension_array_.begin(); it != extension_array_.end(); ++it) { + (*it)->PostExec(); + } // Print simulation speed info PrintStatistics(); // Print helper message for tracing @@ -122,6 +83,37 @@ void VerilatorSimCtrl::RunSimulation() { } } +void VerilatorSimCtrl::SetInitialResetDelay(unsigned int cycles) { + initial_reset_delay_cycles_ = cycles; +} + +void VerilatorSimCtrl::SetResetDuration(unsigned int cycles) { + reset_duration_cycles_ = cycles; +} + +void VerilatorSimCtrl::RequestStop(bool simulation_success) { + request_stop_ = true; + simulation_success_ &= simulation_success; +} + +void VerilatorSimCtrl::RegisterExtension(SimCtrlExtension *ext) { + extension_array_.push_back(ext); +} + +VerilatorSimCtrl::VerilatorSimCtrl() + : top_(nullptr), + time_(0), + tracing_enabled_(false), + tracing_enabled_changed_(false), + tracing_ever_enabled_(false), + tracing_possible_(VM_TRACE), + initial_reset_delay_cycles_(2), + reset_duration_cycles_(2), + request_stop_(false), + simulation_success_(true), + tracer_(VerilatedTracer()), + term_after_cycles_(0) {} + void VerilatorSimCtrl::RegisterSignalHandler() { struct sigaction sigIntHandler; @@ -150,274 +142,15 @@ void VerilatorSimCtrl::SignalHandler(int sig) { } } -void VerilatorSimCtrl::RequestStop(bool simulation_success) { - request_stop_ = true; - simulation_success_ &= simulation_success; -} - -bool VerilatorSimCtrl::TraceOn() { - bool old_tracing_enabled = tracing_enabled_; - - tracing_enabled_ = tracing_possible_; - tracing_ever_enabled_ = tracing_enabled_; - - if (old_tracing_enabled != tracing_enabled_) { - tracing_enabled_changed_ = true; - } - return tracing_enabled_; -} - -bool VerilatorSimCtrl::TraceOff() { - if (tracing_enabled_) { - tracing_enabled_changed_ = true; - } - tracing_enabled_ = false; - return tracing_enabled_; -} - -std::string VerilatorSimCtrl::GetName() const { - if (top_) { - return top_->name(); - } - return "unknown"; -} - -void VerilatorSimCtrl::PrintHelp() const { - std::cout << "Execute a simulation model for " << GetName() - << "\n" - "\n"; - if (tracing_possible_) { - std::cout << "-t|--trace\n" - " Write a trace file from the start\n\n"; - } - std::cout << "-r|--rominit=FILE\n" - " Initialize the ROM with FILE (elf/vmem)\n\n" - "-m|--raminit=FILE\n" - " Initialize the RAM with FILE (elf/vmem)\n\n" - "-f|--flashinit=FILE\n" - " Initialize the FLASH with FILE (elf/vmem)\n\n" - "-l|--meminit=NAME,FILE[,TYPE]\n" - " Initialize memory region NAME with FILE [of TYPE]\n" - " TYPE is either 'elf' or 'vmem'\n\n" - "-l list|--meminit=list\n" - " Print registered memory regions\n\n" - "-c|--term-after-cycles=N\n" - " Terminate simulation after N cycles\n\n" - "-h|--help\n" - " Show help\n\n" - "All further arguments are passed to the design and can be used " - "in the design, e.g. by DPI modules.\n"; -} - -bool VerilatorSimCtrl::RegisterMemoryArea(const std::string name, - const std::string location) { - MemArea mem = {.name = name, .location = location}; - - auto ret = mem_register_.emplace(name, mem); - if (ret.second == false) { - std::cerr << "ERROR: Can not register \"" << name << "\" at: \"" << location - << "\" (Previously defined at: \"" << ret.first->second.location - << "\")" << std::endl; - return false; - } - return true; -} - -MemImageType VerilatorSimCtrl::GetMemImageTypeByName(const std::string name) { - if (name.compare("elf") == 0) { - return kMemImageElf; - } - if (name.compare("vmem") == 0) { - return kMemImageVmem; - } - return kMemImageUnknown; -} - -MemImageType VerilatorSimCtrl::DetectMemImageType(const std::string filepath) { - size_t ext_pos = filepath.find_last_of("."); - std::string ext = filepath.substr(ext_pos + 1); - - if (ext_pos == std::string::npos) { - // Assume ELF files if no file extension is given. - // TODO: Make this more robust by actually checking the file contents. - return kMemImageElf; - } - return GetMemImageTypeByName(ext); -} - -void VerilatorSimCtrl::PrintMemRegions() const { - std::cout << "Registered memory regions:" << std::endl; - for (const auto &m : mem_register_) { - std::cout << "\t'" << m.second.name << "' at location: '" - << m.second.location << "'" << std::endl; - } -} - -bool VerilatorSimCtrl::ParseMemArg(std::string mem_argument, std::string &name, - std::string &filepath, MemImageType &type) { - std::array args; - size_t pos = 0; - size_t end_pos = 0; - size_t i; - - for (i = 0; i < 3; ++i) { - end_pos = mem_argument.find(",", pos); - // Check for possible exit conditions - if (pos == end_pos) { - std::cerr << "ERROR: empty field in: " << mem_argument << std::endl; - return false; - } - if (end_pos == std::string::npos) { - args[i] = mem_argument.substr(pos); - break; - } - args[i] = mem_argument.substr(pos, end_pos - pos); - pos = end_pos + 1; - } - // mem_argument is not empty as getopt requires an argument, - // but not a valid argument for memory initialization - if (i == 0) { - std::cerr << "ERROR: meminit must be in \"name,file[,type]\"" - << " got: " << mem_argument << std::endl; - return false; - } - - name = args[0]; - filepath = args[1]; - - if (i == 1) { - // Type not set explicitly - type = DetectMemImageType(filepath); - } else { - type = GetMemImageTypeByName(args[2]); - } - - return true; -} - -bool VerilatorSimCtrl::MemWrite(const std::string &name, - const std::string &filepath) { - MemImageType type = DetectMemImageType(filepath); - if (type == kMemImageUnknown) { - std::cerr << "ERROR: Unable to detect file type for: " << filepath - << std::endl; - // Continuing for more error messages - } - return MemWrite(name, filepath, type); -} - -bool VerilatorSimCtrl::MemWrite(const std::string &name, - const std::string &filepath, - MemImageType type) { - // Search for corresponding registered memory based on the name - auto it = mem_register_.find(name); - if (it == mem_register_.end()) { - std::cerr << "ERROR: Memory location not set for: '" << name << "'" - << std::endl; - PrintMemRegions(); - return false; - } - - if (!MemWrite(it->second, filepath, type)) { - std::cerr << "ERROR: Setting memory '" << name << "' failed." << std::endl; - return false; - } - return true; -} - -bool VerilatorSimCtrl::MemWrite(const MemArea &m, const std::string &filepath, - MemImageType type) { - if (!IsFileReadable(filepath)) { - std::cerr << "ERROR: Memory initialization file " - << "'" << filepath << "'" - << " is not readable." << std::endl; - return false; - } - - svScope scope = svGetScopeFromName(m.location.data()); - if (!scope) { - std::cerr << "ERROR: No memory found at " << m.location << std::endl; - return false; - } - - switch (type) { - case kMemImageElf: - if (!WriteElfToMem(scope, filepath)) { - std::cerr << "ERROR: Writing ELF file to memory \"" << m.name << "\" (" - << m.location << ") failed." << std::endl; - return false; - } - break; - case kMemImageVmem: - if (!WriteVmemToMem(scope, filepath)) { - std::cerr << "ERROR: Writing VMEM file to memory \"" << m.name << "\" (" - << m.location << ") failed." << std::endl; - return false; - } - break; - case kMemImageUnknown: - default: - std::cerr << "ERROR: Unknown file type for " << m.name << std::endl; - return false; - } - return true; -} - -bool VerilatorSimCtrl::WriteElfToMem(const svScope &scope, - const std::string &filepath) { - bool retcode; - svScope prev_scope = svSetScope(scope); - - uint8_t *buf = nullptr; - size_t len_bytes; - - if (!ElfFileToBinary(filepath, &buf, len_bytes)) { - std::cerr << "ERROR: Could not load: " << filepath << std::endl; - retcode = false; - goto ret; - } - for (int i = 0; i < len_bytes / 4; ++i) { - if (!simutil_verilator_set_mem(i, (svLogicVecVal *)&buf[4 * i])) { - std::cerr << "ERROR: Could not set memory byte: " << i * 4 << "/" - << len_bytes << "" << std::endl; - - retcode = false; - goto ret; - } - } - - retcode = true; - -ret: - svSetScope(prev_scope); - free(buf); - return retcode; -} - -bool VerilatorSimCtrl::WriteVmemToMem(const svScope &scope, - const std::string &filepath) { - svScope prev_scope = svSetScope(scope); - - // TODO: Add error handling. - simutil_verilator_memload(filepath.data()); - - svSetScope(prev_scope); - return true; -} - bool VerilatorSimCtrl::ParseCommandArgs(int argc, char **argv, bool &exit_app) { const struct option long_options[] = { - {"rominit", required_argument, nullptr, 'r'}, - {"raminit", required_argument, nullptr, 'm'}, - {"flashinit", required_argument, nullptr, 'f'}, - {"meminit", required_argument, nullptr, 'l'}, {"term-after-cycles", required_argument, nullptr, 'c'}, {"trace", no_argument, nullptr, 't'}, {"help", no_argument, nullptr, 'h'}, {nullptr, no_argument, nullptr, 0}}; while (1) { - int c = getopt_long(argc, argv, ":r:m:f:l:c:th", long_options, nullptr); + int c = getopt_long(argc, argv, ":c:th", long_options, nullptr); if (c == -1) { break; } @@ -428,44 +161,6 @@ bool VerilatorSimCtrl::ParseCommandArgs(int argc, char **argv, bool &exit_app) { switch (c) { case 0: break; - case 'r': - if (!MemWrite("rom", optarg)) { - std::cerr << "ERROR: Unable to initialize memory." << std::endl; - return false; - } - break; - case 'm': - if (!MemWrite("ram", optarg)) { - std::cerr << "ERROR: Unable to initialize memory." << std::endl; - return false; - } - break; - case 'f': - if (!MemWrite("flash", optarg)) { - std::cerr << "ERROR: Unable to initialize memory." << std::endl; - return false; - } - break; - case 'l': { - if (strcasecmp(optarg, "list") == 0) { - PrintMemRegions(); - exit_app = true; - return true; - } - - std::string name; - std::string filepath; - MemImageType type; - if (!ParseMemArg(optarg, name, filepath, type)) { - std::cerr << "ERROR: Unable to parse meminit arguments." << std::endl; - return false; - } - - if (!MemWrite(name, filepath, type)) { - std::cerr << "ERROR: Unable to initialize memory." << std::endl; - return false; - } - } break; case 't': if (!tracing_possible_) { std::cerr << "ERROR: Tracing has not been enabled at compile time." @@ -491,34 +186,74 @@ bool VerilatorSimCtrl::ParseCommandArgs(int argc, char **argv, bool &exit_app) { } } + // Pass args to verilator Verilated::commandArgs(argc, argv); + + // Parse arguments for all registered extensions + for (auto it = extension_array_.begin(); it != extension_array_.end(); ++it) { + if (!(*it)->ParseCLIArguments(argc, argv, exit_app)) { + return false; + if (exit_app) { + return true; + } + } + } return true; } -void VerilatorSimCtrl::Trace() { - // We cannot output a message when calling TraceOn()/TraceOff() as these - // functions can be called from a signal handler. Instead we print the message - // here from the main loop. - if (tracing_enabled_changed_) { - if (TracingEnabled()) { - std::cout << "Tracing enabled." << std::endl; - } else { - std::cout << "Tracing disabled." << std::endl; - } - tracing_enabled_changed_ = false; +void VerilatorSimCtrl::PrintHelp() const { + std::cout << "Execute a simulation model for " << GetName() + << "\n" + "\n"; + if (tracing_possible_) { + std::cout << "-t|--trace\n" + " Write a trace file from the start\n\n"; } + std::cout << "-c|--term-after-cycles=N\n" + " Terminate simulation after N cycles\n\n" + "-h|--help\n" + " Show help\n\n" + "All further arguments are passed to the design and can be used " + "in the design, e.g. by DPI modules.\n"; +} - if (!TracingEnabled()) { - return; +bool VerilatorSimCtrl::TraceOn() { + bool old_tracing_enabled = tracing_enabled_; + + tracing_enabled_ = tracing_possible_; + tracing_ever_enabled_ = tracing_enabled_; + + if (old_tracing_enabled != tracing_enabled_) { + tracing_enabled_changed_ = true; } + return tracing_enabled_; +} - if (!tracer_.isOpen()) { - tracer_.open(GetTraceFileName()); - std::cout << "Writing simulation traces to " << GetTraceFileName() - << std::endl; +bool VerilatorSimCtrl::TraceOff() { + if (tracing_enabled_) { + tracing_enabled_changed_ = true; } + tracing_enabled_ = false; + return tracing_enabled_; +} - tracer_.dump(GetTime()); +void VerilatorSimCtrl::PrintStatistics() const { + double speed_hz = time_ / 2 / (GetExecutionTimeMs() / 1000.0); + double speed_khz = speed_hz / 1000.0; + + std::cout << std::endl + << "Simulation statistics" << std::endl + << "=====================" << std::endl + << "Executed cycles: " << time_ / 2 << std::endl + << "Wallclock time: " << GetExecutionTimeMs() / 1000.0 << " s" + << std::endl + << "Simulation speed: " << speed_hz << " cycles/s " + << "(" << speed_khz << " kHz)" << std::endl; + + int trace_size_byte; + if (tracing_enabled_ && FileSize(GetTraceFileName(), trace_size_byte)) { + std::cout << "Trace file size: " << trace_size_byte << " B" << std::endl; + } } const char *VerilatorSimCtrl::GetTraceFileName() const { @@ -529,10 +264,6 @@ const char *VerilatorSimCtrl::GetTraceFileName() const { #endif } -void VerilatorSimCtrl::SetOnClockCallback(SimCtrlCallBack callback) { - callback_ = callback; -} - void VerilatorSimCtrl::Run() { assert(top_ && "Use SetTop() first."); @@ -561,8 +292,12 @@ void VerilatorSimCtrl::Run() { *sig_clk_ = !*sig_clk_; - if (*sig_clk_ && (callback_ != nullptr)) { - callback_(time_); + // Call all extension on-clock methods + if (*sig_clk_) { + for (auto it = extension_array_.begin(); it != extension_array_.end(); + ++it) { + (*it)->OnClock(time_); + } } top_->eval(); @@ -595,6 +330,19 @@ void VerilatorSimCtrl::Run() { } } +std::string VerilatorSimCtrl::GetName() const { + if (top_) { + return top_->name(); + } + return "unknown"; +} + +unsigned int VerilatorSimCtrl::GetExecutionTimeMs() const { + return std::chrono::duration_cast(time_end_ - + time_begin_) + .count(); +} + void VerilatorSimCtrl::SetReset() { if (flags_ & ResetPolarityNegative) { *sig_rst_ = 0; @@ -611,19 +359,6 @@ void VerilatorSimCtrl::UnsetReset() { } } -void VerilatorSimCtrl::SetInitialResetDelay(unsigned int cycles) { - initial_reset_delay_cycles_ = cycles; -} - -void VerilatorSimCtrl::SetResetDuration(unsigned int cycles) { - reset_duration_cycles_ = cycles; -} - -bool VerilatorSimCtrl::IsFileReadable(std::string filepath) const { - struct stat statbuf; - return stat(filepath.data(), &statbuf) == 0; -} - bool VerilatorSimCtrl::FileSize(std::string filepath, int &size_byte) const { struct stat statbuf; if (stat(filepath.data(), &statbuf) != 0) { @@ -635,124 +370,28 @@ bool VerilatorSimCtrl::FileSize(std::string filepath, int &size_byte) const { return true; } -unsigned int VerilatorSimCtrl::GetExecutionTimeMs() const { - return std::chrono::duration_cast(time_end_ - - time_begin_) - .count(); -} - -void VerilatorSimCtrl::PrintStatistics() const { - double speed_hz = time_ / 2 / (GetExecutionTimeMs() / 1000.0); - double speed_khz = speed_hz / 1000.0; - - std::cout << std::endl - << "Simulation statistics" << std::endl - << "=====================" << std::endl - << "Executed cycles: " << time_ / 2 << std::endl - << "Wallclock time: " << GetExecutionTimeMs() / 1000.0 << " s" - << std::endl - << "Simulation speed: " << speed_hz << " cycles/s " - << "(" << speed_khz << " kHz)" << std::endl; - - int trace_size_byte; - if (tracing_enabled_ && FileSize(GetTraceFileName(), trace_size_byte)) { - std::cout << "Trace file size: " << trace_size_byte << " B" << std::endl; - } -} - -bool VerilatorSimCtrl::ElfFileToBinary(const std::string &filepath, - uint8_t **data, - size_t &len_bytes) const { - bool retval; - std::list buffers; - size_t offset = 0; - (void)elf_errno(); - len_bytes = 0; - - if (elf_version(EV_CURRENT) == EV_NONE) { - std::cerr << elf_errmsg(-1) << std::endl; - return false; - } - - int fd = open(filepath.c_str(), O_RDONLY, 0); - if (fd < 0) { - std::cerr << "Could not open file: " << filepath << std::endl; - return false; - } - - Elf *elf_desc; - elf_desc = elf_begin(fd, ELF_C_READ, NULL); - if (elf_desc == NULL) { - std::cerr << elf_errmsg(-1) << " in: " << filepath << std::endl; - retval = false; - goto return_fd_end; - } - if (elf_kind(elf_desc) != ELF_K_ELF) { - std::cerr << "Not a ELF file: " << filepath << std::endl; - retval = false; - goto return_elf_end; - } - // TODO: add support for ELFCLASS64 - if (gelf_getclass(elf_desc) != ELFCLASS32) { - std::cerr << "Not a 32-bit ELF file: " << filepath << std::endl; - retval = false; - goto return_elf_end; - } - - size_t phnum; - if (elf_getphdrnum(elf_desc, &phnum) != 0) { - std::cerr << elf_errmsg(-1) << " in: " << filepath << std::endl; - retval = false; - goto return_elf_end; - } - - GElf_Phdr phdr; - Elf_Data *elf_data; - elf_data = NULL; - for (size_t i = 0; i < phnum; i++) { - if (gelf_getphdr(elf_desc, i, &phdr) == NULL) { - std::cerr << elf_errmsg(-1) << " segment number: " << i - << " in: " << filepath << std::endl; - retval = false; - goto return_elf_end; +void VerilatorSimCtrl::Trace() { + // We cannot output a message when calling TraceOn()/TraceOff() as these + // functions can be called from a signal handler. Instead we print the message + // here from the main loop. + if (tracing_enabled_changed_) { + if (TracingEnabled()) { + std::cout << "Tracing enabled." << std::endl; + } else { + std::cout << "Tracing disabled." << std::endl; } - if (phdr.p_type != PT_LOAD) { - std::cout << "Program header number " << i << "is not of type PT_LOAD." - << "Continue." << std::endl; - continue; - } - elf_data = elf_getdata_rawchunk(elf_desc, phdr.p_offset, phdr.p_filesz, - ELF_T_BYTE); - - if (elf_data == NULL) { - retval = false; - goto return_elf_end; - } - - BufferDesc buf_data; - buf_data.length = elf_data->d_size; - len_bytes += buf_data.length; - buf_data.data = (uint8_t *)malloc(elf_data->d_size); - memcpy(buf_data.data, ((uint8_t *)elf_data->d_buf), buf_data.length); - buffers.push_back(buf_data); + tracing_enabled_changed_ = false; } - // Put the collected data into a continuous buffer - // Memory is freed by the caller - *data = (uint8_t *)malloc(len_bytes); - for (std::list::iterator it = buffers.begin(); - it != buffers.end(); ++it) { - memcpy(((uint8_t *)*data) + offset, it->data, it->length); - offset += it->length; - free(it->data); + if (!TracingEnabled()) { + return; } - buffers.clear(); - retval = true; + if (!tracer_.isOpen()) { + tracer_.open(GetTraceFileName()); + std::cout << "Writing simulation traces to " << GetTraceFileName() + << std::endl; + } -return_elf_end: - elf_end(elf_desc); -return_fd_end: - close(fd); - return retval; + tracer_.dump(GetTime()); } diff --git a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h index efa9c604..c371ee8d 100644 --- a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h +++ b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h @@ -6,12 +6,10 @@ #define VERILATOR_SIM_CTRL_H_ #include -#include -#include #include +#include -#include - +#include "sim_ctrl_extension.h" #include "verilated_toplevel.h" enum VerilatorSimCtrlFlags { @@ -19,21 +17,6 @@ enum VerilatorSimCtrlFlags { ResetPolarityNegative = 1, }; -// Callback function to be called every clock cycle -// The parameter is simulation time -typedef std::function SimCtrlCallBack; - -enum MemImageType { - kMemImageUnknown = 0, - kMemImageElf, - kMemImageVmem, -}; - -struct MemArea { - std::string name; // Unique identifier - std::string location; // Design scope location -}; - /** * Simulation controller for verilated simulations */ @@ -44,10 +27,10 @@ class VerilatorSimCtrl { * * @see SetTop() */ - static VerilatorSimCtrl& GetInstance(); + static VerilatorSimCtrl &GetInstance(); - VerilatorSimCtrl(VerilatorSimCtrl const&) = delete; - void operator=(VerilatorSimCtrl const&) = delete; + VerilatorSimCtrl(VerilatorSimCtrl const &) = delete; + void operator=(VerilatorSimCtrl const &) = delete; /** * Set the top-level design @@ -84,51 +67,6 @@ class VerilatorSimCtrl { */ void RunSimulation(); - /** - * Print help how to use this tool - */ - void PrintHelp() const; - - /** - * Run the main loop of the simulation - * - * This function blocks until the simulation finishes. - */ - void Run(); - - /** - * Register a memory as instantiated by generic ram - * - * The |name| must be a unique identifier. The function will return false - * if |name| is already used. |location| is the path to the scope of the - * instantiated memory, which needs to support the DPI-C interfaces - * 'simutil_verilator_memload' and 'simutil_verilator_set_mem' used for - * 'vmem' and 'elf' files, respectively. - * - * Memories must be registered before command arguments are parsed by - * ParseCommandArgs() in order for them to be known. - */ - bool RegisterMemoryArea(const std::string name, const std::string location); - - /** - * Print a list of all registered memory regions - * - * @see RegisterMemoryArea() - */ - void PrintMemRegions() const; - - /** - * Get the current time in ticks - */ - unsigned long GetTime() const { return time_; } - - /** - * Get a name for this simulation - * - * This name is typically the name of the top-level. - */ - std::string GetName() const; - /** * Get the simulation result */ @@ -150,6 +88,71 @@ class VerilatorSimCtrl { */ void RequestStop(bool simulation_success); + /** + * Register an extension to be called automatically + */ + void RegisterExtension(SimCtrlExtension *ext); + + /** + * Get the current time in ticks + */ + unsigned long GetTime() const { return time_; } + + private: + VerilatedToplevel *top_; + CData *sig_clk_; + CData *sig_rst_; + VerilatorSimCtrlFlags flags_; + unsigned long time_; + bool tracing_enabled_; + bool tracing_enabled_changed_; + bool tracing_ever_enabled_; + bool tracing_possible_; + unsigned int initial_reset_delay_cycles_; + unsigned int reset_duration_cycles_; + volatile unsigned int request_stop_; + volatile bool simulation_success_; + std::chrono::steady_clock::time_point time_begin_; + std::chrono::steady_clock::time_point time_end_; + VerilatedTracer tracer_; + int term_after_cycles_; + std::vector extension_array_; + + /** + * Default constructor + * + * Use GetInstance() instead. + */ + VerilatorSimCtrl(); + + /** + * Register the signal handler + */ + void RegisterSignalHandler(); + + /** + * Signal handler callback + * + * Use RegisterSignalHandler() to setup. + */ + static void SignalHandler(int sig); + + /** + * Parse command line arguments + * + * Process all recognized command-line arguments from argc/argv. + * + * @param argc, argv Standard C command line arguments + * @param exit_app Indicate that program should terminate + * @return Return code, true == success + */ + bool ParseCommandArgs(int argc, char **argv, bool &exit_app); + + /** + * Print help how to use this tool + */ + void PrintHelp() const; + /** * Enable tracing (if possible) * @@ -195,91 +198,43 @@ class VerilatorSimCtrl { const char *GetTraceFileName() const; /** - * Set a callback function to run every cycle + * Run the main loop of the simulation + * + * This function blocks until the simulation finishes. */ - void SetOnClockCallback(SimCtrlCallBack callback); - - private: - VerilatedToplevel *top_; - CData *sig_clk_; - CData *sig_rst_; - VerilatorSimCtrlFlags flags_; - unsigned long time_; - bool tracing_enabled_; - bool tracing_enabled_changed_; - bool tracing_ever_enabled_; - bool tracing_possible_; - unsigned int initial_reset_delay_cycles_; - unsigned int reset_duration_cycles_; - volatile unsigned int request_stop_; - volatile bool simulation_success_; - std::chrono::steady_clock::time_point time_begin_; - std::chrono::steady_clock::time_point time_end_; - VerilatedTracer tracer_; - std::map mem_register_; - int term_after_cycles_; - SimCtrlCallBack callback_; + void Run(); /** - * Default constructor + * Get a name for this simulation * - * Use GetInstance() instead. + * This name is typically the name of the top-level. */ - VerilatorSimCtrl(); + std::string GetName() const; /** - * Register the signal handler + * Get the wallclock execution time in ms */ - void RegisterSignalHandler(); - - /** - * Signal handler callback - * - * Use RegisterSignalHandler() to setup. - */ - static void SignalHandler(int sig); - - /** - * Parse command line arguments - * - * This removes all recognized command-line arguments from argc/argv. - * - * The return value of this method indicates if the program should exit with - * retcode: if this method returns true, do *not* exit; if it returns *false*, - * do exit. - */ - bool ParseCommandArgs(int argc, char **argv, bool &exit_app); - - /** - * Parse argument section specific to memory initialization. - * - * Must be in the form of: name,file[,type]. - */ - bool ParseMemArg(std::string mem_argument, std::string &name, - std::string &filepath, MemImageType &type); - MemImageType DetectMemImageType(const std::string filepath); - MemImageType GetMemImageTypeByName(const std::string name); - unsigned int GetExecutionTimeMs() const; - void SetReset(); - void UnsetReset(); - bool IsFileReadable(std::string filepath) const; - bool FileSize(std::string filepath, int &size_byte) const; - void Trace(); /** - * Dump an ELF file into a raw binary + * Assert the reset signal */ - bool ElfFileToBinary(const std::string &filepath, uint8_t **data, - size_t &len_bytes) const; + void SetReset(); - bool MemWrite(const std::string &name, const std::string &filepath); - bool MemWrite(const std::string &name, const std::string &filepath, - MemImageType type); - bool MemWrite(const MemArea &m, const std::string &filepath, - MemImageType type); - bool WriteElfToMem(const svScope &scope, const std::string &filepath); - bool WriteVmemToMem(const svScope &scope, const std::string &filepath); + /** + * Deassert the reset signal + */ + void UnsetReset(); + + /** + * Return the size of a file + */ + bool FileSize(std::string filepath, int &size_byte) const; + + /** + * Perform tracing in Verilator if required + */ + void Trace(); }; #endif // VERILATOR_SIM_CTRL_H_ diff --git a/dv/verilator/simutil_verilator/simutil_verilator.core b/dv/verilator/simutil_verilator/simutil_verilator.core index b7d5b9ba..d14327ae 100644 --- a/dv/verilator/simutil_verilator/simutil_verilator.core +++ b/dv/verilator/simutil_verilator/simutil_verilator.core @@ -12,6 +12,7 @@ filesets: - cpp/verilated_toplevel.cc - cpp/verilator_sim_ctrl.h: { is_include_file: true } - cpp/verilated_toplevel.h: { is_include_file: true } + - cpp/sim_ctrl_extension.h: { is_include_file: true } file_type: cppSource targets: