diff --git a/dv/verilator/simutil_verilator/cpp/verilated_toplevel.h b/dv/verilator/simutil_verilator/cpp/verilated_toplevel.h new file mode 100644 index 00000000..e40d23b4 --- /dev/null +++ b/dv/verilator/simutil_verilator/cpp/verilated_toplevel.h @@ -0,0 +1,133 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef VERILATED_TOPLEVEL_H_ +#define VERILATED_TOPLEVEL_H_ + +#include + +// VM_TRACE_FMT_FST must be set by the user when calling Verilator with +// --trace-fst. VM_TRACE is set by Verilator itself. +#if VM_TRACE == 1 +# ifdef VM_TRACE_FMT_FST +# include "verilated_fst_c.h" +# define VM_TRACE_CLASS_NAME VerilatedFstC +# else +# include "verilated_vcd_c.h" +# define VM_TRACE_CLASS_NAME VerilatedVcdC +# endif +#endif + +#if VM_TRACE == 1 +/** + * "Base" for all tracers in Verilator with common functionality + * + * This class is (like the VerilatedToplevel class) a workaround for the + * insufficient class hierarchy in Verilator-generated C++ code. + * + * Once Verilator is improved to support this functionality natively this class + * should go away. + */ +class VerilatedTracer { + public: + VerilatedTracer() : impl_(nullptr) { + impl_ = new VM_TRACE_CLASS_NAME(); + }; + + ~VerilatedTracer() { + delete impl_; + } + + + bool isOpen() const { + return impl_->isOpen(); + }; + + void open(const char* filename) { + impl_->open(filename); + }; + + void close() { + impl_->close(); + }; + + void dump(vluint64_t timeui) { + impl_->dump(timeui); + } + + operator VM_TRACE_CLASS_NAME*() const { assert(impl_); return impl_; } + + private: + VM_TRACE_CLASS_NAME* impl_; +}; +#else +/** + * No-op tracer interface + */ +class VerilatedTracer { + public: + VerilatedTracer() {}; + ~VerilatedTracer() {} + bool isOpen() const { return false; }; + void open(const char* filename) {}; + void close() {}; + void dump(vluint64_t timeui) {} +}; +#endif // VM_TRACE == 1 + +/** + * Pure abstract class (interface) for verilated toplevel modules + * + * Verilator-produced toplevel modules do not have a common base class defining + * the methods such as eval(); instead, they are only inheriting from the + * generic VerilatedModule class, which doesn't have toplevel-specific + * functionality. This makes it impossible to write code which accepts any + * toplevel module as input by specifying the common "toplevel base class". + * + * This class, VerilatedToplevel, fills this gap by defining an abstract base + * class for verilated toplevel modules. This class should be used together with + * the VERILATED_TOPLEVEL macro. + * + * Note that this function is a workaround until Verilator gains this + * functionality natively. + * + * To support the different tracing implementations (VCD, FST or no tracing), + * the trace() function is modified to take a VerilatedTracer argument instead + * of the tracer-specific class. + */ +class VerilatedToplevel { +public: + VerilatedToplevel() {}; + virtual ~VerilatedToplevel() {}; + + virtual void eval() = 0; + virtual void final() = 0; + virtual const char* name() const = 0; + virtual void trace(VerilatedTracer& tfp, int levels, int options) = 0; +}; + +#define STR(s) #s + +#if VM_TRACE == 1 +#define VERILATED_TOPLEVEL_TRACE_CALL(topname) \ + V##topname::trace(static_cast(tfp), levels, options); +#else +#define VERILATED_TOPLEVEL_TRACE_CALL(topname) \ + assert(0 && "Tracing not enabled."); +#endif + + +#define VERILATED_TOPLEVEL(topname) \ + class topname : public V##topname, public VerilatedToplevel { \ + public: \ + topname(const char* name="TOP") : V##topname(name), VerilatedToplevel() {} \ + const char* name() const { return STR(topname); } \ + void eval() { V##topname::eval(); } \ + void final() { V##topname::final(); } \ + void trace(VerilatedTracer& tfp, int levels, int options=0) { \ + VERILATED_TOPLEVEL_TRACE_CALL(topname) \ + } \ + }; + +#endif // VERILATED_TOPLEVEL_H_ diff --git a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc new file mode 100644 index 00000000..95fa841c --- /dev/null +++ b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.cc @@ -0,0 +1,381 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#include "verilator_sim_ctrl.h" + +#include +#include +#include +#include + +#include + +// This is defined by Verilator and passed through the command line +#ifndef VM_TRACE +#define VM_TRACE 0 +#endif + +// DPI Exports +extern "C" { +extern void simutil_verilator_memload(const char* file); +} + +VerilatorSimCtrl::VerilatorSimCtrl(VerilatedToplevel* top, CData& sig_clk, + CData& sig_rst, VerilatorSimCtrlFlags flags) + : top_(top), + sig_clk_(sig_clk), + sig_rst_(sig_rst), + flags_(flags), + time_(0), + init_rom_(false), + init_ram_(false), + init_flash_(false), + 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), + tracer_(VerilatedTracer()), + term_after_cycles_(0) {} + +void VerilatorSimCtrl::RequestStop() { request_stop_ = true; } + +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_; +} + +void VerilatorSimCtrl::PrintHelp() const { + std::cout << "Execute a simulation model for " << top_->name() + << "\n" + "\n"; + if (tracing_possible_) { + std::cout << "-t|--trace Write a trace file from the start\n"; + } + std::cout << "-r|--rominit=VMEMFILE Initialize the ROM with VMEMFILE\n" + "-m|--raminit=VMEMFILE Initialize the RAM with VMEMFILE\n" + "-f|--flashinit=VMEMFILE Initialize the FLASH with VMEMFILE\n" + "-h|--help Show help\n" + "\n" + "All further arguments are passed to the design and can be used " + "in the \n" + "design, e.g. by DPI modules.\n"; +} + +void VerilatorSimCtrl::InitRom(std::string rom) { + if (!init_rom_) { + return; + } + + svScope scope; + + scope = svGetScopeFromName(rom.data()); + if (!scope) { + std::cerr << "ERROR: No ROM found at " << rom << std::endl; + exit(1); + } + svSetScope(scope); + + simutil_verilator_memload(rom_init_file_.data()); + + std::cout << std::endl + << "Rom initialized with program at " << rom_init_file_ + << std::endl; +} + +void VerilatorSimCtrl::InitRam(std::string ram) { + if (!init_ram_) { + return; + } + + svScope scope; + + scope = svGetScopeFromName(ram.data()); + if (!scope) { + std::cerr << "ERROR: No RAM found at " << ram << std::endl; + exit(1); + } + svSetScope(scope); + + simutil_verilator_memload(ram_init_file_.data()); + + std::cout << std::endl + << "Ram initialized with program at " << ram_init_file_ + << std::endl; +} + +void VerilatorSimCtrl::InitFlash(std::string flash) { + if (!init_flash_) { + return; + } + + svScope scope; + + scope = svGetScopeFromName(flash.data()); + if (!scope) { + std::cerr << "ERROR: No FLASH found at " << flash << std::endl; + exit(1); + } + svSetScope(scope); + + simutil_verilator_memload(flash_init_file_.data()); + + std::cout << std::endl + << "Flash initialized with program at " << flash_init_file_ + << std::endl; +} + +bool VerilatorSimCtrl::ParseCommandArgs(int argc, char** argv, int& retcode) { + const struct option long_options[] = { + {"rominit", required_argument, nullptr, 'r'}, + {"raminit", required_argument, nullptr, 'm'}, + {"flashinit", required_argument, nullptr, 'f'}, + {"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:th", long_options, nullptr); + if (c == -1) { + break; + } + + // Disable error reporting by getopt + opterr = 0; + + switch (c) { + case 0: + break; + case 'r': + rom_init_file_ = optarg; + init_rom_ = true; + if (!IsFileReadable(rom_init_file_)) { + std::cerr << "ERROR: ROM initialization file " + << "'" << rom_init_file_ << "'" + << " is not readable." << std::endl; + return false; + } + break; + case 'm': + ram_init_file_ = optarg; + init_ram_ = true; + if (!IsFileReadable(ram_init_file_)) { + std::cerr << "ERROR: Memory initialization file " + << "'" << ram_init_file_ << "'" + << " is not readable." << std::endl; + return false; + } + break; + case 'f': + flash_init_file_ = optarg; + init_flash_ = true; + if (!IsFileReadable(flash_init_file_)) { + std::cerr << "ERROR: FLASH initialization file " + << "'" << flash_init_file_ << "'" + << " is not readable." << std::endl; + return false; + } + break; + case 't': + if (!tracing_possible_) { + std::cerr << "ERROR: Tracing has not been enabled at compile time." + << std::endl; + return false; + } + TraceOn(); + break; + case 'c': + term_after_cycles_ = atoi(optarg); + break; + case 'h': + PrintHelp(); + return false; + case ':': // missing argument + std::cerr << "ERROR: Missing argument." << std::endl; + PrintHelp(); + return false; + case '?': + default:; + // Ignore unrecognized options since they might be consumed by + // Verilator's built-in parsing below. + } + } + + Verilated::commandArgs(argc, argv); + 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; + } + + if (!TracingEnabled()) { + return; + } + + if (!tracer_.isOpen()) { + tracer_.open(GetSimulationFileName()); + std::cout << "Writing simulation traces to " << GetSimulationFileName() + << std::endl; + } + + tracer_.dump(GetTime()); +} + +const char* VerilatorSimCtrl::GetSimulationFileName() const { +#ifdef VM_TRACE_FMT_FST + return "sim.fst"; +#else + return "sim.vcd"; +#endif +} + +void VerilatorSimCtrl::Run() { + // We always need to enable this as tracing can be enabled at runtime + if (tracing_possible_) { + Verilated::traceEverOn(true); + top_->trace(tracer_, 99, 0); + } + + // Evaluate all initial blocks, including the DPI setup routines + top_->eval(); + + std::cout << std::endl + << "Simulation running, end by pressing CTRL-c." << std::endl; + + time_begin_ = std::chrono::steady_clock::now(); + UnsetReset(); + Trace(); + while (1) { + if (time_ >= initial_reset_delay_cycles_ * 2) { + SetReset(); + } + if (time_ >= reset_duration_cycles_ * 2 + initial_reset_delay_cycles_ * 2) { + UnsetReset(); + } + + sig_clk_ = !sig_clk_; + + top_->eval(); + time_++; + + Trace(); + + if (request_stop_) { + std::cout << "Received stop request, shutting down simulation." + << std::endl; + break; + } + if (Verilated::gotFinish()) { + std::cout << "Received $finish() from Verilog, shutting down simulation." + << std::endl; + break; + } + if (term_after_cycles_ && time_ > term_after_cycles_) { + std::cout << "Simulation timeout of " << term_after_cycles_ + << " cycles reached, shutting down simulation." + << std::endl; + break; + } + } + + top_->final(); + time_end_ = std::chrono::steady_clock::now(); + + if (TracingEverEnabled()) { + tracer_.close(); + } +} + +void VerilatorSimCtrl::SetReset() { + if (flags_ & ResetPolarityNegative) { + sig_rst_ = 0; + } else { + sig_rst_ = 1; + } +} + +void VerilatorSimCtrl::UnsetReset() { + if (flags_ & ResetPolarityNegative) { + sig_rst_ = 1; + } else { + sig_rst_ = 0; + } +} + +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) { + struct stat statbuf; + return stat(filepath.data(), &statbuf) == 0; +} + +bool VerilatorSimCtrl::FileSize(std::string filepath, int& size_byte) { + struct stat statbuf; + if (stat(filepath.data(), &statbuf) != 0) { + size_byte = 0; + return false; + } + + size_byte = statbuf.st_size; + return true; +} + +unsigned int VerilatorSimCtrl::GetExecutionTimeMs() { + return std::chrono::duration_cast(time_end_ - + time_begin_) + .count(); +} + +void VerilatorSimCtrl::PrintStatistics() { + 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(GetSimulationFileName(), trace_size_byte)) { + std::cout << "Trace file size: " << trace_size_byte << " B" << std::endl; + } +} diff --git a/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h new file mode 100644 index 00000000..ef629c85 --- /dev/null +++ b/dv/verilator/simutil_verilator/cpp/verilator_sim_ctrl.h @@ -0,0 +1,157 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +#ifndef VERILATOR_SIM_CTRL_H_ +#define VERILATOR_SIM_CTRL_H_ + +#include + +#include +#include + +#include "verilated_toplevel.h" + +enum VerilatorSimCtrlFlags { + Defaults = 0, + ResetPolarityNegative = 1, +}; + +class VerilatorSimCtrl { + public: + VerilatorSimCtrl(VerilatedToplevel* top, CData& clk, CData& rst_n, + VerilatorSimCtrlFlags flags = Defaults); + + /** + * Print help how to use this tool + */ + void PrintHelp() const; + + /** + * 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, int& retcode); + + /** + * Run the main loop of the simulation + * + * This function blocks until the simulation finishes. + */ + void Run(); + + /** + * Initialize Rom + */ + void InitRom(const std::string mem); + + /** + * Initialize Ram + */ + void InitRam(const std::string mem); + + /** + * Initialize Flash + */ + void InitFlash(const std::string mem); + + /** + * Get the current time in ticks + */ + unsigned long GetTime() { return time_; } + + /** + * Set the number of clock cycles (periods) before the reset signal is + * activated + */ + void SetInitialResetDelay(unsigned int cycles); + + /** + * Set the number of clock cycles (periods) the reset signal is activated + */ + void SetResetDuration(unsigned int cycles); + + /** + * Request the simulation to stop + */ + void RequestStop(); + + /** + * Enable tracing (if possible) + * + * Enabling tracing can fail if no tracing support has been compiled into the + * simulation. + * + * @return Is tracing enabled? + */ + bool TraceOn(); + + /** + * Disable tracing + * + * @return Is tracing enabled? + */ + bool TraceOff(); + + /** + * Is tracing currently enabled? + */ + bool TracingEnabled() { return tracing_enabled_; } + + /** + * Has tracing been ever enabled during the run? + * + * Tracing can be enabled and disabled at runtime. + */ + bool TracingEverEnabled() { return tracing_ever_enabled_; } + + /** + * Is tracing support compiled into the simulation? + */ + bool TracingPossible() { return tracing_possible_; } + + /** + * Print statistics about the simulation run + */ + void PrintStatistics(); + + const char* GetSimulationFileName() const; + + private: + VerilatedToplevel* top_; + CData& sig_clk_; + CData& sig_rst_; + VerilatorSimCtrlFlags flags_; + unsigned long time_; + bool init_rom_; + bool init_ram_; + bool init_flash_; + std::string rom_init_file_; + std::string ram_init_file_; + std::string flash_init_file_; + 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_; + std::chrono::steady_clock::time_point time_begin_; + std::chrono::steady_clock::time_point time_end_; + VerilatedTracer tracer_; + int term_after_cycles_; + + unsigned int GetExecutionTimeMs(); + void SetReset(); + void UnsetReset(); + bool IsFileReadable(std::string filepath); + bool FileSize(std::string filepath, int& size_byte); + 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 new file mode 100644 index 00000000..28b45f17 --- /dev/null +++ b/dv/verilator/simutil_verilator/simutil_verilator.core @@ -0,0 +1,19 @@ +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:simutil_verilator" +description: "Verilator simulator support" +filesets: + files_cpp: + files: + - cpp/verilator_sim_ctrl.cc + - cpp/verilator_sim_ctrl.h: { is_include_file: true } + - cpp/verilated_toplevel.h: { is_include_file: true } + file_type: cppSource + +targets: + default: + filesets: + - files_cpp