DV: Add verilator simulation utility

This commit is contained in:
Philipp Wagner 2019-08-03 01:31:12 +01:00 committed by Philipp Wagner
parent 677153f549
commit b72f5db6bd
4 changed files with 690 additions and 0 deletions

View file

@ -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 <verilated.h>
// 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<VM_TRACE_CLASS_NAME*>(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_

View file

@ -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 <getopt.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vltstd/svdpi.h>
#include <iostream>
// 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<std::chrono::milliseconds>(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;
}
}

View file

@ -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 <verilated.h>
#include <chrono>
#include <string>
#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_

View file

@ -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