|  | // Copyright 2025 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 "tests/systemc/instruction_trace.h" | 
|  | #include <cassert> | 
|  | #include <cstdio> | 
|  |  | 
|  | constexpr uint32_t kEcallInst = 0x00000073; | 
|  |  | 
|  | void InstructionTrace::TraceInstruction( | 
|  | const std::vector<bool>& fires, | 
|  | const std::vector<uint32_t>& addrs, | 
|  | const std::vector<uint32_t>& insts, | 
|  | const std::vector<bool>& scalarWriteAddrValids, | 
|  | const std::vector<uint32_t>& scalarWriteAddrAddrs, | 
|  | const std::vector<bool>& floatWriteAddrValids, | 
|  | const std::vector<uint32_t>& floatWriteAddrAddrs, | 
|  | const std::vector<bool>& writeDataValids, | 
|  | const std::vector<uint32_t>& writeDataAddrs, | 
|  | const std::vector<uint32_t>& writeDataDatas, | 
|  | const std::vector<int>& executeRegBases | 
|  | ) { | 
|  | assert(fires.size() == addrs.size()); | 
|  | assert(fires.size() == insts.size()); | 
|  | assert(fires.size() == scalarWriteAddrValids.size()); | 
|  | assert(scalarWriteAddrValids.size() == scalarWriteAddrAddrs.size()); | 
|  | assert(floatWriteAddrValids.size() == floatWriteAddrAddrs.size()); | 
|  | assert(writeDataValids.size() == writeDataAddrs.size()); | 
|  | assert(writeDataValids.size() == writeDataDatas.size()); | 
|  | assert(writeDataValids.size() == executeRegBases.size()); | 
|  |  | 
|  | // Push data about the instructions that were dispatched this cycle into | 
|  | // the retirement buffer. Newly queued instructions are marked as incomplete. | 
|  | for (size_t i = 0; i < floatWriteAddrValids.size(); ++i) { | 
|  | bool fire = fires[i]; | 
|  | bool valid = floatWriteAddrValids[i]; | 
|  | uint32_t inst = insts[i]; | 
|  | uint32_t pc = addrs[i]; | 
|  | int reg = floatWriteAddrAddrs[i] + kFloatBaseReg; | 
|  | if (fire && valid) { | 
|  | Instruction in(pc, inst, reg); | 
|  | retirement_buffer_.push_back(in); | 
|  | } | 
|  | } | 
|  | for (size_t i = 0; i < scalarWriteAddrValids.size(); ++i) { | 
|  | bool fire = fires[i]; | 
|  | bool valid = scalarWriteAddrValids[i]; | 
|  | uint32_t inst = insts[i]; | 
|  | uint32_t pc = addrs[i]; | 
|  | int reg = scalarWriteAddrAddrs[i] + kScalarBaseReg; | 
|  | if (fire && valid && (reg != 0)) { | 
|  | Instruction in(pc, inst, reg); | 
|  | retirement_buffer_.push_back(in); | 
|  | } | 
|  | if (fire && (inst == kEcallInst)) { | 
|  | Instruction in(pc, inst, kEcallBaseReg); | 
|  | retirement_buffer_.push_back(in); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Iterate over the write ports, and find the first incomplete instruction | 
|  | // that matches the write. Mark that instruction as completed, and move | 
|  | // to the next write port. | 
|  | for (size_t i = 0; i < writeDataValids.size(); ++i) { | 
|  | bool valid = writeDataValids[i]; | 
|  | uint32_t addr = writeDataAddrs[i] + executeRegBases[i]; | 
|  | uint32_t data = writeDataDatas[i]; | 
|  | for (auto& in : retirement_buffer_) { | 
|  | if (in.completed) continue; | 
|  | if (valid && (addr == in.reg)) { | 
|  | in.data = data; | 
|  | in.completed = true; | 
|  | break; | 
|  | } | 
|  | if (in.inst == kEcallInst) { | 
|  | in.completed = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Iterate over the retirement buffer, moving completed instructions | 
|  | // from the front into the committed_insts_ buffer. | 
|  | // When we see an incomplete instruction, stop. | 
|  | while (!retirement_buffer_.empty()) { | 
|  | auto in = retirement_buffer_.front(); | 
|  | if (!in.completed) { | 
|  | break; | 
|  | } else { | 
|  | committed_insts_.push_back(in); | 
|  | retirement_buffer_.pop_front(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void InstructionTrace::TraceInstructionRaw(uint32_t pc, uint32_t inst, uint32_t reg, uint32_t data) { | 
|  | Instruction in(pc, inst, reg); | 
|  | in.data = data; | 
|  | committed_insts_.push_back(in); | 
|  | } | 
|  |  | 
|  | void InstructionTrace::PrintTrace() const { | 
|  | for (auto& inst : committed_insts_) { | 
|  | printf("0x%08x,0x%08x,0x%02x,0x%08x\n", inst.pc, inst.inst, inst.reg, inst.data); | 
|  | } | 
|  | } |