mirror of
https://github.com/openhwgroup/cve2.git
synced 2025-04-22 04:57:25 -04:00
[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:
parent
347b80f631
commit
e45e314686
8 changed files with 832 additions and 640 deletions
|
@ -1,4 +1,4 @@
|
|||
# Verilator simulator support
|
||||
# Verilator memory loading support
|
||||
|
||||
## ELF support
|
||||
|
433
dv/verilator/memutil/cpp/verilator_memutil.cc
Normal file
433
dv/verilator/memutil/cpp/verilator_memutil.cc
Normal 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;
|
||||
}
|
105
dv/verilator/memutil/cpp/verilator_memutil.h
Normal file
105
dv/verilator/memutil/cpp/verilator_memutil.h
Normal 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_
|
20
dv/verilator/memutil/memutil_verilator.core
Normal file
20
dv/verilator/memutil/memutil_verilator.core
Normal 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
|
39
dv/verilator/simutil_verilator/cpp/sim_ctrl_extension.h
Normal file
39
dv/verilator/simutil_verilator/cpp/sim_ctrl_extension.h
Normal 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_
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue