[verilator] Separate out memory loading utilities

- Split memory utils out of VerilatorSimCtrl
- Allows VerilatorSimCtrl to be used in systems not requiring memory loading
- Fixes #317
This commit is contained in:
Tom Roberts 2019-11-21 08:32:02 +00:00 committed by Philipp Wagner
parent 347b80f631
commit e45e314686
8 changed files with 832 additions and 640 deletions

View file

@ -1,4 +1,4 @@
# Verilator simulator support
# Verilator memory loading support
## ELF support

View file

@ -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 <fcntl.h>
#include <gelf.h>
#include <getopt.h>
#include <libelf.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <list>
// 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<std::string, 3> 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<BufferDesc> 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<BufferDesc>::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;
}

View file

@ -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 <vltstd/svdpi.h>
#include <map>
#include <string>
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<std::string, MemArea> 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_

View file

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

View file

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

View file

@ -4,79 +4,32 @@
#include "verilator_sim_ctrl.h"
#include <fcntl.h>
#include <gelf.h>
#include <getopt.h>
#include <libelf.h>
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>
#include <vltstd/svdpi.h>
#include <verilated.h>
#include <iostream>
#include <map>
#include <utility>
// 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<std::string, 3> 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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(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<BufferDesc> 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<BufferDesc>::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());
}

View file

@ -6,12 +6,10 @@
#define VERILATOR_SIM_CTRL_H_
#include <chrono>
#include <functional>
#include <map>
#include <string>
#include <vector>
#include <verilated.h>
#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<void(int /* sim time */)> 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<SimCtrlExtension *> 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<std::string, MemArea> 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_

View file

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