blob: 9174c63f55a367d30abfa82077a729abeafc7a01 [file] [log] [blame]
// Copyright 2024 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 <memory>
#include <optional>
#include <ostream>
#include <string>
#include <utility>
#include <vector>
#include "absl/flags/flag.h"
#include "absl/flags/parse.h"
#include "absl/flags/usage.h"
#include "absl/functional/any_invocable.h"
#include "absl/functional/bind_front.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/status/status.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 "cheriot/cheriot_decoder.h"
#include "cheriot/cheriot_instrumentation_control.h"
#include "cheriot/cheriot_top.h"
#include "cheriot/debug_command_shell.h"
#include "cheriot/riscv_cheriot_minstret.h"
#include "mpact/sim/generic/core_debug_interface.h"
#include "mpact/sim/generic/counters.h"
#include "mpact/sim/generic/instruction.h"
#include "mpact/sim/proto/component_data.pb.h"
#include "mpact/sim/util/memory/atomic_memory.h"
#include "mpact/sim/util/memory/memory_interface.h"
#include "mpact/sim/util/memory/memory_use_profiler.h"
#include "mpact/sim/util/memory/memory_watcher.h"
#include "mpact/sim/util/memory/single_initiator_router.h"
#include "mpact/sim/util/memory/tagged_flat_demand_memory.h"
#include "mpact/sim/util/memory/tagged_memory_interface.h"
#include "mpact/sim/util/memory/tagged_memory_watcher.h"
#include "mpact/sim/util/other/instruction_profiler.h"
#include "mpact/sim/util/other/simple_uart.h"
#include "mpact/sim/util/program_loader/elf_program_loader.h"
#include "re2/re2.h"
#include "riscv//riscv_arm_semihost.h"
#include "riscv//riscv_clint.h"
#include "src/google/protobuf/text_format.h"
using AddressRange = mpact::sim::util::MemoryWatcher::AddressRange;
using ::mpact::sim::cheriot::CheriotDecoder;
using ::mpact::sim::cheriot::CheriotInstrumentationControl;
using ::mpact::sim::cheriot::CheriotState;
using ::mpact::sim::proto::ComponentData;
using ::mpact::sim::util::InstructionProfiler;
using ::mpact::sim::util::TaggedMemoryUseProfiler;
// Flags for specifying interactive mode.
ABSL_FLAG(bool, i, false, "Interactive mode");
ABSL_FLAG(bool, interactive, false, "Interactive mode");
// Flag for destination directory of proto file.
ABSL_FLAG(std::string, output_dir, "", "Output directory");
// The following defines the optional flag for setting the stack size. If the
// stack size is not set using the flag, then the simulator will look in the
// executable to see if the GNU_STACK segment exists (assuming gcc RiscV
// compiler), and use that size. If not, it will use the value of the symbol
// __stack_size in the executable. If no such symbol exists, the stack size will
// be 32KB.
//
// A symbol may be defined in a C/C++ source file using asm, such as:
// asm(".global __stack_size\n"
// ".equ __stack_size, 32 * 1024\n");
// The asm statement need not be inside a function body.
//
// The program header entry may be generated by adding the following to the
// gcc/g++ command line: -Wl,z,stack-size=N
//
ABSL_FLAG(std::optional<uint64_t>, stack_size, 32 * 1024,
"Size of software stack");
// Optional flag for setting the location of the end of the stack (bottom). The
// beginning stack pointer is the value stack_end + stack_size. If this option
// is not set, it will use the value of the symbol __stack_end in the
// executable. If no such symbol exists, stack pointer initialization will not
// be performed by the simulator, and an appropriate crt0 library has to be
// used.
//
// A symbol may be defined in a C/C++ source file using asm, such as:
// asm(".global __stack_end\n"
// ".equ __stack_end, 0x200000\n");
// The asm statement need not be inside a function body.
ABSL_FLAG(std::optional<uint64_t>, stack_end, 0,
"Lowest valid address of software stack. "
"Top of stack is stack_end + stack_size.");
// The following macro can be used in source code to define both the stack size
// and location:
//
// #define __STACK(addr, size) \
// asm(".global __stack_size\n.equ __stack_size, " #size "\n"); \
// asm(".global __stack_end\n.equ __stack_end, " #addr "\n");
//
// E.g.
//
// #include <stdio>
//
// __STACK(0x20000, 32 * 1024);
//
// int main(int, char **) {
// printf("Hello World\n");
// return 0;
// }
//
// Flag to exit on any exception and print the relevant exception information.
ABSL_FLAG(bool, exit_on_exception, false, "Exit on exception");
// Enable instruction profiling.
ABSL_FLAG(bool, inst_profile, false, "Enable instruction profiling");
// Enable memory use profiling.
ABSL_FLAG(bool, mem_profile, false, "Enable memory use profiling");
constexpr char kStackEndSymbolName[] = "__stack_end";
constexpr char kStackSizeSymbolName[] = "__stack_size";
constexpr int kCapabilityGranule = 8;
using HaltReason = ::mpact::sim::generic::CoreDebugInterface::HaltReason;
using ::mpact::sim::cheriot::CheriotTop;
using ::mpact::sim::cheriot::RiscVCheriotMInstret;
using ::mpact::sim::cheriot::RiscVCheriotMInstreth;
using ::mpact::sim::generic::Instruction;
using ::mpact::sim::riscv::RiscVArmSemihost;
using ::mpact::sim::riscv::RiscVClint;
using ::mpact::sim::util::AtomicMemoryOpInterface;
using ::mpact::sim::util::MemoryInterface;
using ::mpact::sim::util::SimpleUart;
using ::mpact::sim::util::TaggedMemoryInterface;
using ::mpact::sim::util::TaggedMemoryWatcher;
// Static pointer to the top instance. Used by the control-C handler.
static CheriotTop *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);
}
}
// This is an example custom command that is added to the interactive
// debug command shell.
static bool PrintRegisters(
absl::string_view input,
const mpact::sim::cheriot::DebugCommandShell::CoreAccess &core_access,
std::string &output) {
static const LazyRE2 reg_info_re{R"(\s*xyzreg\s+info\s*)"};
if (!RE2::FullMatch(input, *reg_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()) {
output = absl::StrCat("Failed to read register '", reg_name, "'");
return true;
}
output_str +=
absl::StrCat("x", absl::Dec(i, absl::kZeroPad2), " = [",
absl::Hex(result.value(), absl::kZeroPad8), "]\n");
}
output = output_str;
return true;
}
// Trap handler.
bool HandleSimulatorTrap(bool is_interrupt, uint64_t trap_value, uint64_t ec,
uint64_t epc, const Instruction *instruction) {
if (is_interrupt) return false;
std::cerr << absl::StrCat(
"Exception\n"
" trapvalue: ",
absl::Hex(trap_value, absl::kZeroPad8),
"\n"
" code: ",
absl::Hex(ec, absl::kZeroPad8),
"\n"
" epc: ",
absl::Hex(epc, absl::kZeroPad8),
"\n"
" inst: ",
instruction == nullptr ? "nullptr" : instruction->AsString(), "\n");
// Halt the simulation.
if (top != nullptr) (void)top->Halt();
return false;
}
// Main function for the simulator.
int main(int argc, char **argv) {
absl::SetProgramUsageMessage(argv[0]);
auto arg_vec = absl::ParseCommandLine(argc, argv);
if (arg_vec.size() > 2) {
std::cerr << "Only a single input file allowed" << std::endl;
return -1;
}
if (arg_vec.size() < 2) {
std::cerr << "Must specify input file" << std::endl;
return -1;
}
std::string full_file_name = arg_vec[1];
std::string file_name =
full_file_name.substr(full_file_name.find_last_of('/') + 1);
std::string file_basename = file_name.substr(0, file_name.find_first_of('.'));
auto *tagged_memory =
new mpact::sim::util::TaggedFlatDemandMemory(kCapabilityGranule);
// Load the elf segments into memory.
mpact::sim::util::ElfProgramLoader elf_loader(tagged_memory);
auto load_result = elf_loader.LoadProgram(full_file_name);
if (!load_result.ok()) {
std::cerr << "Error while loading '" << full_file_name
<< "': " << load_result.status().message();
return -1;
}
auto *router = new mpact::sim::util::SingleInitiatorRouter("router");
TaggedMemoryInterface *data_memory =
static_cast<TaggedMemoryInterface *>(router);
TaggedMemoryUseProfiler *memory_use_profiler = nullptr;
// Check to see if memory use profiling is enabled, and if so, set it up.
if (absl::GetFlag(FLAGS_mem_profile)) {
memory_use_profiler = new TaggedMemoryUseProfiler(data_memory);
// Disable until program execution.
memory_use_profiler->set_is_enabled(false);
data_memory = memory_use_profiler;
}
CheriotState cheriot_state("CherIoT", data_memory,
static_cast<AtomicMemoryOpInterface *>(router));
CheriotDecoder cheriot_decoder(&cheriot_state,
static_cast<MemoryInterface *>(router));
CheriotTop cheriot_top("Cheriot", &cheriot_state, &cheriot_decoder);
// Enable instruction profiling if the flag is set.
InstructionProfiler *inst_profiler = nullptr;
if (absl::GetFlag(FLAGS_inst_profile)) {
inst_profiler = new InstructionProfiler(elf_loader, 2);
cheriot_top.counter_pc()->AddListener(inst_profiler);
} else {
cheriot_top.counter_pc()->SetIsEnabled(false);
}
mpact::sim::generic::DataBuffer *db = nullptr;
// If tohost exists, add a memory watcher to look for exit signal.
auto tohost_res = elf_loader.GetSymbol("tohost");
uint64_t tohost_addr = 0;
if (tohost_res.ok()) {
// tohost is declared as uint32_t tohost[2]. Writing an lsb of 1
// terminates the simulation. The upper 31 bits can pass extra metadata.
// Use all 0s to indicate success.
tohost_addr = tohost_res.value().first;
// Add to_host watchpoint.
db = cheriot_top.state()->db_factory()->Allocate<uint32_t>(2);
auto status = cheriot_top.tagged_watcher()->SetStoreWatchCallback(
TaggedMemoryWatcher::AddressRange{
tohost_addr, tohost_addr + 2 * sizeof(uint32_t) - 1},
[tagged_memory, tohost_addr, &db, &cheriot_top](uint64_t, int) {
if (db == nullptr) return;
tagged_memory->Load(tohost_addr, db, nullptr, nullptr);
uint32_t code = db->Get<uint32_t>(0);
if (code & 0x1) {
code >>= 1;
std::cerr << absl::StrCat("Simulation halted: exit ",
absl::Hex(code), "\n");
(void)cheriot_top.Halt();
db->DecRef();
db = nullptr;
}
});
if (!status.ok()) {
LOG(ERROR) << "Failed to set 'tohost' watchpoint";
exit(-1);
}
}
// Initialize minstret/minstreth. Bind the instruction counter to those
// registers.
auto minstret_res = cheriot_top.state()->csr_set()->GetCsr("minstret");
auto minstreth_res = cheriot_top.state()->csr_set()->GetCsr("minstreth");
if (!minstret_res.ok() || !minstreth_res.ok()) {
std::cerr << "Error while initializing minstret/minstreth";
return -1;
}
auto *minstret = static_cast<RiscVCheriotMInstret *>(minstret_res.value());
auto *minstreth = static_cast<RiscVCheriotMInstreth *>(minstreth_res.value());
minstret->set_counter(cheriot_top.counter_num_instructions());
minstreth->set_counter(cheriot_top.counter_num_instructions());
// Set up the memory router with the appropriate targets.
::mpact::sim::util::AtomicMemory *atomic_memory = nullptr;
atomic_memory = new mpact::sim::util::AtomicMemory(tagged_memory);
auto *uart = new SimpleUart(cheriot_top.state());
CHECK_OK(
router->AddTarget<MemoryInterface>(uart, 0x1000'0000ULL, 0x1000'00ffULL));
auto *clint = new RiscVClint(/*period=*/100, cheriot_top.state()->mip());
cheriot_top.counter_num_cycles()->AddListener(clint);
CHECK_OK(router->AddTarget<AtomicMemoryOpInterface>(
atomic_memory, 0x0000'0000ULL, 0x01ff'ffffULL));
CHECK_OK(router->AddTarget<TaggedMemoryInterface>(
tagged_memory, 0x0000'0000ULL, 0x01ff'ffffULL));
CHECK_OK(router->AddTarget<MemoryInterface>(clint, 0x0200'0000ULL,
0x0200'ffffULL));
CHECK_OK(router->AddTarget<AtomicMemoryOpInterface>(
atomic_memory, 0x02001'0000ULL, 0xffff'ffffULL));
CHECK_OK(router->AddTarget<TaggedMemoryInterface>(
tagged_memory, 0x0201'0000ULL, 0xffff'ffffULL));
// Set up a dummy WFI handler.
cheriot_top.state()->set_on_wfi([](const Instruction *) { return true; });
cheriot_top.state()->set_on_ecall([](const Instruction *) { return false; });
// Initialize the PC to the entry point.
uint32_t entry_point = load_result.value();
auto pcc_write = cheriot_top.WriteRegister("pcc", entry_point);
if (!pcc_write.ok()) {
std::cerr << "Error writing to pcc: " << pcc_write.message();
return -1;
}
// Set up semihosting.
auto *memory = static_cast<MemoryInterface *>(router);
auto *semihost =
new RiscVArmSemihost(RiscVArmSemihost::BitWidth::kWord32, memory, memory);
cheriot_top.state()->AddEbreakHandler([semihost](const Instruction *inst) {
if (semihost->IsSemihostingCall(inst)) {
semihost->OnEBreak(inst);
return true;
}
return false;
});
semihost->set_exit_callback([&cheriot_top]() {
cheriot_top.RequestHalt(HaltReason::kSemihostHaltRequest, nullptr);
});
// Initializing the stack pointer.
// First see if there is a stack location defined, if not, do not initialize
// the stack pointer.
bool initialize_stack = false;
uint64_t stack_end = 0;
// Is the __stack_end symbol defined?
auto res = elf_loader.GetSymbol(kStackEndSymbolName);
if (res.ok()) {
stack_end = res.value().first;
initialize_stack = true;
}
// The stack_end flag overrides the __stack_end symbol.
if (absl::GetFlag(FLAGS_stack_end).has_value()) {
stack_end = absl::GetFlag(FLAGS_stack_end).value();
initialize_stack = true;
}
// If there is a stack location, get the stack size, and write the sp.
if (initialize_stack) {
// Default size is 32KB.
uint64_t stack_size = 32 * 1024;
// Does the executable have a valid GNU_STACK segment? If so, override the
// default
auto loader_res = elf_loader.GetStackSize();
if (loader_res.ok()) {
stack_end = loader_res.value();
}
// If the __stack_size symbol is defined then override.
auto res = elf_loader.GetSymbol(kStackSizeSymbolName);
if (res.ok()) {
stack_size = res.value().first;
}
// If the flag is set, then override.
if (absl::GetFlag(FLAGS_stack_size).has_value()) {
stack_size = absl::GetFlag(FLAGS_stack_size).value();
}
auto sp_write = cheriot_top.WriteRegister("sp", stack_end + stack_size);
if (!sp_write.ok()) {
std::cerr << "Error writing to sp: " << sp_write.message();
return -1;
}
}
mpact::sim::generic::SimpleCounter<double> counter_sec("simulation_time_sec",
0.0);
CHECK_OK(cheriot_top.AddCounter(&counter_sec));
// Set up control-c handling.
top = &cheriot_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);
// If exit on exception is set, set up an exception handler that terminates
// the simulation and prints exception information. In interactive mode a
// message is printed and any run is stopped.
if (absl::GetFlag(FLAGS_exit_on_exception)) {
cheriot_top.state()->set_on_trap(&HandleSimulatorTrap);
}
if (memory_use_profiler) memory_use_profiler->set_is_enabled(true);
// Determine if this is being run interactively or as a batch job.
bool interactive = absl::GetFlag(FLAGS_i) || absl::GetFlag(FLAGS_interactive);
CheriotInstrumentationControl *cheriot_instrumentation_control = nullptr;
if (interactive) {
mpact::sim::cheriot::DebugCommandShell cmd_shell;
cmd_shell.AddCore({&cheriot_top, [&elf_loader]() { return &elf_loader; }});
cheriot_instrumentation_control = new CheriotInstrumentationControl(
&cmd_shell, &cheriot_top, memory_use_profiler);
// Add custom command to interactive debug command shell.
cmd_shell.AddCommand(
" reg info - print all scalar regs",
PrintRegisters);
cmd_shell.AddCommand(
cheriot_instrumentation_control->Usage(),
absl::bind_front(&CheriotInstrumentationControl::PerformShellCommand,
cheriot_instrumentation_control));
cmd_shell.Run(std::cin, std::cout);
} else {
std::cerr << "Starting simulation\n";
auto t0 = absl::Now();
auto run_status = cheriot_top.Run();
if (!run_status.ok()) {
std::cerr << run_status.message() << std::endl;
}
auto wait_status = cheriot_top.Wait();
if (!wait_status.ok()) {
std::cerr << wait_status.message() << std::endl;
}
auto t1 = absl::Now();
absl::Duration duration = t1 - t0;
double sec = static_cast<double>(duration / absl::Milliseconds(100)) / 10;
counter_sec.SetValue(sec);
std::cerr << absl::StrFormat("Simulation done: %0.1f sec\n", sec);
}
// Write out memory use profile.
if (memory_use_profiler != nullptr) {
std::cerr << "Writing out memory use profile\n";
std::string memory_use_profile_file_name;
if (FLAGS_output_dir.CurrentValue().empty()) {
memory_use_profile_file_name =
"./" + file_basename + "_memory_use_profile.csv";
} else {
memory_use_profile_file_name = FLAGS_output_dir.CurrentValue() + "/" +
file_basename + "_memory_use_profile.csv";
}
std::fstream memory_use_profile_file(memory_use_profile_file_name.c_str(),
std::ios_base::out);
if (!memory_use_profile_file.good()) {
LOG(ERROR) << "Failed to write memory use profile to file";
} else {
memory_use_profiler->WriteProfile(memory_use_profile_file);
}
}
// Write out instruction profile.
if (inst_profiler != nullptr) {
std::cerr << "Writing out instruction profile\n";
std::string inst_profile_file_name;
if (FLAGS_output_dir.CurrentValue().empty()) {
inst_profile_file_name = "./" + file_basename + "_inst_profile.csv";
} else {
inst_profile_file_name = FLAGS_output_dir.CurrentValue() + "/" +
file_basename + "_inst_profile.csv";
}
std::fstream inst_profile_file(inst_profile_file_name.c_str(),
std::ios_base::out);
if (!inst_profile_file.good()) {
LOG(ERROR) << "Failed to write profile to file";
} else {
inst_profiler->WriteProfile(inst_profile_file);
}
}
// Export counters.
std::cerr << "Exporting counters\n";
auto component_proto = std::make_unique<ComponentData>();
CHECK_OK(cheriot_top.Export(component_proto.get()))
<< "Failed to export proto";
std::string proto_file_name;
if (FLAGS_output_dir.CurrentValue().empty()) {
proto_file_name = "./" + file_basename + "_counters.proto";
} else {
proto_file_name = FLAGS_output_dir.CurrentValue() + "/" + file_basename +
"_counters.proto";
}
std::fstream proto_file(proto_file_name.c_str(), std::ios_base::out);
std::string serialized;
if (!proto_file.good() || !google::protobuf::TextFormat::PrintToString(
*component_proto.get(), &serialized)) {
LOG(ERROR) << "Failed to write proto to file";
} else {
proto_file << serialized;
proto_file.close();
}
// Cleanup.
auto bp_status = cheriot_top.ClearAllSwBreakpoints();
if (!bp_status.ok()) {
LOG(ERROR) << "Error in ClearAllSwBreakpoints: " << bp_status.message();
}
delete cheriot_instrumentation_control;
delete inst_profiler;
delete atomic_memory;
delete tagged_memory;
delete memory_use_profiler;
delete semihost;
if (db != nullptr) db->DecRef();
}