blob: 38f268b7da3510eac99d3574a250e8b4a1dad35b [file] [log] [blame]
// Copyright 2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <signal.h>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <ios>
#include <iostream>
#include <optional>
#include <string>
#include <vector>
#include "sim/kelvin_state.h"
#include "sim/kelvin_top.h"
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/flags/usage.h"
#include "absl/log/initialize.h"
#include "absl/log/log.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "riscv/debug_command_shell.h"
#include "riscv/riscv_register_aliases.h"
#include "mpact/sim/util/program_loader/elf_program_loader.h"
#include "re2/re2.h"
// Flags for specifying interactive mode.
ABSL_FLAG(bool, i, false, "Interactive mode");
ABSL_FLAG(bool, interactive, false, "Interactive mode");
ABSL_FLAG(uint32_t, bin_memory_offset, 0,
"Memory offset to load the binary file");
ABSL_FLAG(std::optional<uint32_t>, entry_point, std::nullopt,
"Optionally set the entry point of the program.");
// Static pointer to the top instance. Used by the control-C handler.
static kelvin::sim::KelvinTop *top = nullptr;
// Control-c handler to interrupt any running simulation.
static void sim_sigint_handler(int arg) {
if (top != nullptr) {
(void)top->Halt();
return;
} else {
exit(-1);
}
}
// Custom debug command to print all the scalar register values.
static bool PrintRegisters(
absl::string_view input,
const mpact::sim::riscv::DebugCommandShell::CoreAccess &core_access,
std::string &output) {
LazyRE2 xreg_info_re{R"(\s*reg\s+info\s*)"};
if (!RE2::FullMatch(input, *xreg_info_re)) {
return false;
}
std::string output_str;
for (int i = 0; i < 32; ++i) {
std::string reg_name = absl::StrCat("x", i);
auto result = core_access.debug_interface->ReadRegister(reg_name);
if (!result.ok()) {
// Skip the register if error occurs.
continue;
}
output_str +=
absl::StrCat(mpact::sim::riscv::kXRegisterAliases[i], "\t = [",
absl::Hex(result.value(), absl::kZeroPad8), "]\n");
}
output = output_str;
return true;
}
// Custom debug command to print all the assigned vector register values.
static bool PrintVectorRegisters(
absl::string_view input,
const mpact::sim::riscv::DebugCommandShell::CoreAccess &core_access,
std::string &output) {
LazyRE2 vreg_info_re{R"(\s*vreg\s+info\s*)"};
if (!RE2::FullMatch(input, *vreg_info_re)) {
return false;
}
std::string output_str;
for (int i = 0; i < kelvin::sim::kNumVregs; ++i) {
std::string reg_name = absl::StrCat("v", i);
auto result = core_access.debug_interface->GetRegisterDataBuffer(reg_name);
if (!result.ok()) {
// Skip the register if error occurs.
continue;
}
auto *db = result.value();
if (db == nullptr) {
// Skip the register if the data buffer is not available.
continue;
}
std::string data_str;
std::string sep;
for (int j = 0; j < kelvin::sim::kVectorLengthInBits / 32; ++j) {
auto value = db->Get<uint32_t>(j);
data_str += sep + absl::StrFormat("%08x", value);
sep = ":";
}
output_str += absl::StrCat("v", i, "\t = [", data_str, "]\n");
}
output = output_str;
return true;
}
// Use ELF file's magic word to determine if the input file is an ELF file.
static bool IsElfFile(std::string &file_name) {
std::ifstream image_file;
image_file.open(file_name, std::ios::in | std::ios::binary);
if (image_file.good()) {
uint32_t magic_word;
image_file.read(reinterpret_cast<char *>(&magic_word), sizeof(magic_word));
image_file.close();
return magic_word == 0x464c457f; // little endian ELF magic word.
}
return false;
}
int main(int argc, char **argv) {
absl::InitializeLog();
absl::SetProgramUsageMessage("Kelvin MPACT-Sim based CLI tool");
auto out_args = absl::ParseCommandLine(argc, argv);
argc = out_args.size();
argv = &out_args[0];
if (argc != 2) {
LOG(ERROR) << "Only a single input file allowed";
return -1;
}
std::string file_name = argv[1];
kelvin::sim::KelvinTop kelvin_top("Kelvin");
// Set up control-c handling.
top = &kelvin_top;
struct sigaction sa;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGINT);
sa.sa_handler = &sim_sigint_handler;
sigaction(SIGINT, &sa, nullptr);
bool interactive = absl::GetFlag(FLAGS_i) || absl::GetFlag(FLAGS_interactive);
auto is_elf_file = IsElfFile(file_name);
uint32_t entry_point = 0;
// Load the elf segments into memory.
mpact::sim::util::ElfProgramLoader elf_loader(kelvin_top.memory());
if (!is_elf_file && interactive) {
LOG(ERROR) << "Interactive mode may misbehave without the ELF symbol";
return -1;
}
if (is_elf_file) {
auto load_result = elf_loader.LoadProgram(file_name);
if (!load_result.ok()) {
LOG(ERROR) << "Error while loading '" << file_name
<< "': " << load_result.status().message();
return -1;
}
auto elf_entry_point = load_result.value();
// Set the program entry point to based on the ELF info but can
// be overridden by the `entry_point` flag.
entry_point = (absl::GetFlag(FLAGS_entry_point).has_value())
? absl::GetFlag(FLAGS_entry_point).value()
: elf_entry_point;
if (elf_entry_point != entry_point) {
LOG(ERROR) << absl::StrFormat(
"ELF recorded entry point 0x%08x is different from the flag value "
"0x%08x. The program may not start properly",
elf_entry_point, entry_point);
}
} else { // Load binary file from the specified memory offset.
// Required the flag `entry_point` to be specified for binary file.
if (!absl::GetFlag(FLAGS_entry_point).has_value()) {
LOG(ERROR) << "Need to specify the program entry point";
return -1;
}
entry_point = absl::GetFlag(FLAGS_entry_point).value();
auto res =
kelvin_top.LoadImage(file_name, absl::GetFlag(FLAGS_bin_memory_offset));
if (!res.ok()) {
LOG(ERROR) << "Error while loading '" << file_name
<< "': " << res.message();
return -1;
}
}
// Initialize the PC to the entry point.
auto pc_write = kelvin_top.WriteRegister("pc", entry_point);
if (!pc_write.ok()) {
LOG(ERROR) << "Error writing to pc: " << pc_write.message();
return -1;
}
// Determine if this is being run interactively or as a batch job.
if (interactive) {
mpact::sim::riscv::DebugCommandShell cmd_shell(
{{&kelvin_top, &elf_loader}});
// Add custom commands to interactive debug command shell.
cmd_shell.AddCommand(
" reg info - print all scalar regs",
PrintRegisters);
cmd_shell.AddCommand(
" vreg info - print assigned vector regs",
PrintVectorRegisters);
cmd_shell.Run(std::cin, std::cout);
} else {
std::cout << "Starting simulation\n";
auto t0 = absl::Now();
auto run_status = kelvin_top.Run();
if (!run_status.ok()) {
LOG(ERROR) << run_status.message();
}
auto wait_status = kelvin_top.Wait();
if (!wait_status.ok()) {
LOG(ERROR) << wait_status.message();
}
auto sec = absl::ToDoubleSeconds(absl::Now() - t0);
std::cout << "Total cycles: " << kelvin_top.GetCycleCount() << '\n';
std::cout << absl::StrFormat("Simulation done: %0.3f sec\n", sec);
}
}