Use protobuf to store simulation trace

PiperOrigin-RevId: 567391644
diff --git a/sim/BUILD b/sim/BUILD
index 8ea30ec..3ced2fe 100644
--- a/sim/BUILD
+++ b/sim/BUILD
@@ -131,6 +131,7 @@
         ":kelvin_decoder",
         ":kelvin_isa",
         ":kelvin_state",
+        "//sim/proto:trace_cc_proto",
         "//sim/renode:kelvin_renode_memory",
         "@com_google_absl//absl/container:flat_hash_map",
         "@com_google_absl//absl/flags:flag",
diff --git a/sim/kelvin_top.cc b/sim/kelvin_top.cc
index 0862639..445df59 100644
--- a/sim/kelvin_top.cc
+++ b/sim/kelvin_top.cc
@@ -8,7 +8,6 @@
 #include <cstdint>
 #include <cstring>
 #include <fstream>
-#include <iomanip>
 #include <ios>
 #include <iostream>
 #include <string>
@@ -18,6 +17,7 @@
 #include "sim/decoder.h"
 #include "sim/kelvin_enums.h"
 #include "sim/kelvin_state.h"
+#include "sim/proto/kelvin_trace.pb.h"
 #include "sim/renode/kelvin_renode_memory.h"
 #include "absl/flags/flag.h"
 #include "absl/functional/bind_front.h"
@@ -41,8 +41,12 @@
 #include "mpact/sim/util/memory/flat_demand_memory.h"
 
 ABSL_FLAG(bool, use_semihost, false, "Use semihost in the simulation");
-ABSL_FLAG(bool, trace, false, "Dump executed instruction trace");
-ABSL_FLAG(std::string, trace_path, "/tmp/kelvin_trace.txt",
+ABSL_FLAG(bool, trace, false,
+          "Dump executed instruction trace as Google Protobuf binary file. The "
+          "output can be decoded with protoc");
+ABSL_FLAG(bool, trace_disasm, false,
+          "Dump the disassembled opcode string with the trace");
+ABSL_FLAG(std::string, trace_path, "/tmp/kelvin_trace.pb",
           "Path to save trace");
 
 namespace kelvin::sim {
@@ -52,8 +56,7 @@
 constexpr char kKelvinName[] = "Kelvin";
 
 // Local helper function used to execute instructions.
-static inline bool ExecuteInstruction(mpact::sim::util::Instruction *inst,
-                                      std::fstream *trace = nullptr) {
+static inline bool ExecuteInstruction(mpact::sim::util::Instruction *inst) {
   for (auto *resource : inst->ResourceHold()) {
     if (!resource->IsFree()) {
       return false;
@@ -62,13 +65,6 @@
   for (auto *resource : inst->ResourceAcquire()) {
     resource->Acquire();
   }
-  if (trace != nullptr) {
-    // TODO(hcindyl): Use protobuf to store the serialized trace.
-    *trace << "["
-           << "0x" << std::setfill('0') << std::setw(8) << std::hex
-           << inst->address() << "] " << inst->AsString() << std::endl;
-  }
-
   inst->Execute(nullptr);
   return true;
 }
@@ -359,13 +355,16 @@
     uint64_t next_seq_pc;
 
     std::fstream trace_file;
+    proto::TraceData trace_data;
+    auto *inst_db = db_factory_.Allocate<uint32_t>(1);
     if (absl::GetFlag(FLAGS_trace)) {
       std::string trace_path = absl::GetFlag(FLAGS_trace_path);
       std::string trace_dir =
           trace_path.substr(0, trace_path.find_last_of('/'));
       int res = mkdir(trace_dir.c_str(), 0777);
       if (res == 0 || errno == EEXIST) {
-        trace_file.open(absl::GetFlag(FLAGS_trace_path), std::ios_base::out);
+        trace_file.open(absl::GetFlag(FLAGS_trace_path),
+                        std::ios_base::out | std::ios_base::binary);
         std::cout << "Dump trace file at " << absl::GetFlag(FLAGS_trace_path)
                   << std::endl;
       } else {
@@ -381,9 +380,19 @@
       // executed will overwrite this.
       SetPc(next_seq_pc);
       bool executed = false;
-      std::fstream *trace_ptr = trace_file.is_open() ? &trace_file : nullptr;
       do {
-        executed = ExecuteInstruction(inst, trace_ptr);
+        executed = ExecuteInstruction(inst);
+        if (trace_file.is_open()) {
+          // Set trace entry {address, instruction}
+          memory_->Load(pc, inst_db, nullptr, nullptr);
+          auto inst_word = inst_db->Get<uint32_t>(0);
+          proto::TraceEntry *trace_entry = trace_data.add_entry();
+          trace_entry->set_address(pc);
+          trace_entry->set_opcode(inst_word);
+          if (absl::GetFlag(FLAGS_trace_disasm)) {
+            trace_entry->set_disasm(inst->AsString());
+          }
+        }
         counter_num_cycles_.Increment(1);
         state_->AdvanceDelayLines();
       } while (!executed);
@@ -404,7 +413,9 @@
     }
     run_status_ = RunStatus::kHalted;
 
+    inst_db->DecRef();
     if (trace_file.is_open()) {
+      trace_data.SerializeToOstream(&trace_file);
       trace_file.close();
     }
     // Notify that the run has completed.
diff --git a/sim/proto/BUILD b/sim/proto/BUILD
new file mode 100644
index 0000000..17f0722
--- /dev/null
+++ b/sim/proto/BUILD
@@ -0,0 +1,19 @@
+# Protobuf for the kelvin sim trace file
+
+
+
+package(default_visibility = ["//visibility:public"])
+
+proto_library(
+    name = "trace_proto",
+    srcs = [
+        "kelvin_trace.proto",
+    ],
+)
+
+cc_proto_library(
+    name = "trace_cc_proto",
+    deps = [
+        ":trace_proto",
+    ],
+)
diff --git a/sim/proto/kelvin_trace.proto b/sim/proto/kelvin_trace.proto
new file mode 100644
index 0000000..2c060a3
--- /dev/null
+++ b/sim/proto/kelvin_trace.proto
@@ -0,0 +1,15 @@
+syntax = "proto2";
+
+package kelvin.sim.proto;
+
+option java_multiple_files = true;
+
+message TraceEntry {
+  optional uint32 address = 1;
+  optional uint32 opcode = 2;
+  optional string disasm = 3;
+}
+
+message TraceData {
+  repeated TraceEntry entry = 1;
+}