| // 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 |
| // |
| // https://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 "riscv/riscv_clint.h" |
| |
| #include <cstdint> |
| #include <cstring> |
| |
| #include "absl/log/log.h" |
| #include "mpact/sim/generic/data_buffer.h" |
| #include "mpact/sim/generic/instruction.h" |
| #include "mpact/sim/generic/ref_count.h" |
| #include "riscv/riscv_state.h" |
| #include "riscv/riscv_xip_xie.h" |
| |
| namespace mpact { |
| namespace sim { |
| namespace riscv { |
| |
| using ::mpact::sim::generic::DataBuffer; |
| using ::mpact::sim::generic::Instruction; |
| using ::mpact::sim::generic::ReferenceCount; |
| using EC = ::mpact::sim::riscv::ExceptionCode; |
| |
| RiscVClint::RiscVClint(int period, MipExternalWriteInterface* mip_interface) |
| : mip_interface_(mip_interface), period_(period) { |
| // Set the initial values. |
| SetValue(0); |
| mip_interface_->set_mtip(mtip_); |
| mip_interface->set_msip(msip_ & 0b1); |
| int bit = mtime_ >= mtimecmp_; |
| if (bit == mtip_) return; |
| mip_interface_->set_mtip(bit); |
| mtip_ = bit; |
| } |
| |
| // Reset of the clint block. |
| void RiscVClint::Reset() { |
| // Reset clears mtime and msip. |
| WriteMTimeLow(0); |
| WriteMTimeHigh(0); |
| WriteMSip(0); |
| } |
| |
| // Called by the counter whenever its value is changed. |
| void RiscVClint::SetValue(const uint64_t& val) { |
| update_counter_++; |
| if (update_counter_ >= period_) { |
| update_counter_ = 0; |
| mtime_++; |
| int bit = mtime_ >= mtimecmp_; |
| if (bit == mtip_) return; |
| mip_interface_->set_mtip(bit); |
| mtip_ = bit; |
| } |
| } |
| |
| // Implementation of the memory load interface for reading memory mapped |
| // registers. |
| void RiscVClint::Load(uint64_t address, DataBuffer* db, Instruction* inst, |
| ReferenceCount* context) { |
| uint32_t offset = address & 0xffff; |
| switch (db->size<uint8_t>()) { |
| case 1: |
| db->Set<uint8_t>(0, static_cast<uint8_t>(Read(offset))); |
| break; |
| case 2: |
| db->Set<uint16_t>(0, static_cast<uint16_t>(Read(offset))); |
| break; |
| case 4: |
| db->Set<uint32_t>(0, static_cast<uint32_t>(Read(offset))); |
| break; |
| case 8: |
| db->Set<uint32_t>(0, static_cast<uint32_t>(Read(offset))); |
| db->Set<uint32_t>(1, static_cast<uint32_t>(Read(offset + 4))); |
| break; |
| default: |
| ::memset(db->raw_ptr(), 0, sizeof(db->size<uint8_t>())); |
| break; |
| } |
| // Execute the instruction to process and write back the load data. |
| if (nullptr != inst) { |
| if (db->latency() > 0) { |
| inst->IncRef(); |
| if (context != nullptr) context->IncRef(); |
| inst->state()->function_delay_line()->Add(db->latency(), |
| [inst, context]() { |
| inst->Execute(context); |
| if (context != nullptr) |
| context->DecRef(); |
| inst->DecRef(); |
| }); |
| } else { |
| inst->Execute(context); |
| } |
| } |
| } |
| |
| // No support for vector loads. |
| void RiscVClint::Load(DataBuffer* address_db, DataBuffer* mask_db, int el_size, |
| DataBuffer* db, Instruction* inst, |
| ReferenceCount* context) { |
| LOG(FATAL) << "RiscVClint does not support vector loads"; |
| } |
| |
| // Implementation of memory store interface to support writes to memory mapped |
| // registers. |
| void RiscVClint::Store(uint64_t address, DataBuffer* db) { |
| uint32_t offset = address & 0xffff; |
| switch (db->size<uint8_t>()) { |
| case 1: |
| return Write(offset, static_cast<uint32_t>(db->Get<uint8_t>(0))); |
| case 2: |
| return Write(offset, static_cast<uint32_t>(db->Get<uint16_t>(0))); |
| case 4: |
| return Write(offset, static_cast<uint32_t>(db->Get<uint32_t>(0))); |
| case 8: |
| return Write(offset, static_cast<uint32_t>(db->Get<uint32_t>(0))); |
| return Write(offset + 4, static_cast<uint32_t>(db->Get<uint32_t>(1))); |
| default: |
| return; |
| } |
| } |
| |
| // No support for vector stores. |
| void RiscVClint::Store(DataBuffer* address, DataBuffer* mask, int el_size, |
| DataBuffer* db) { |
| LOG(FATAL) << "RiscVClint does not support vector stores"; |
| } |
| |
| uint32_t RiscVClint::Read(uint32_t offset) { |
| switch (offset) { |
| case 0x0000: // msip |
| return msip_; |
| case 0x4000: // mtimecmp low |
| return static_cast<uint32_t>(mtimecmp_ & 0xffff'ffffULL); |
| case 0x4004: // mtimecmp high |
| return static_cast<uint32_t>(mtimecmp_ >> 32); |
| case 0xbff8: // mtime low |
| return ReadMTimeLow(); |
| case 0xbffc: // mtime high |
| return ReadMTimeHigh(); |
| default: |
| return 0; |
| } |
| } |
| |
| void RiscVClint::Write(uint32_t offset, uint32_t value) { |
| switch (offset) { |
| case 0x0000: // msip |
| WriteMSip(value); |
| return; |
| case 0x4000: // mtimecmp low |
| WriteMTimeCmpLow(value); |
| return; |
| case 0x4004: // mtimecmp high |
| WriteMTimeCmpHigh(value); |
| return; |
| case 0xbff8: // mtime low |
| WriteMTimeLow(value); |
| update_counter_ = 0; |
| return; |
| case 0xbffc: // mtime high |
| WriteMTimeHigh(value); |
| return; |
| default: |
| // Ignore write. |
| return; |
| } |
| } |
| |
| // Reading mtime is equivalent to reading the cycle counter, extracting the |
| // right half, and adding any offset produced by a previous write to the |
| // register. |
| uint32_t RiscVClint::ReadMTimeLow() { |
| return static_cast<uint32_t>(mtime_ & 0xffff'ffffULL); |
| } |
| |
| uint32_t RiscVClint::ReadMTimeHigh() { |
| return static_cast<uint32_t>(mtime_ >> 32); |
| } |
| |
| // Write to msip updates the msip bit in the mip register. |
| void RiscVClint::WriteMSip(uint32_t value) { |
| msip_ = value & 0b1; |
| mip_interface_->set_msip(msip_ & 0b1); |
| } |
| |
| // Update the time compare registers. |
| void RiscVClint::WriteMTimeCmpLow(uint32_t value) { |
| mtimecmp_ = |
| (mtimecmp_ & 0xffff'ffff'0000'0000ULL) | static_cast<uint64_t>(value); |
| |
| int bit = mtime_ >= mtimecmp_; |
| if (bit == mtip_) return; |
| mip_interface_->set_mtip(bit); |
| mtip_ = bit; |
| } |
| |
| void RiscVClint::WriteMTimeCmpHigh(uint32_t value) { |
| mtimecmp_ = (mtimecmp_ & 0x0000'0000'ffff'ffffULL) | |
| (static_cast<uint64_t>(value) << 32); |
| int bit = mtime_ >= mtimecmp_; |
| if (bit == mtip_) return; |
| mip_interface_->set_mtip(bit); |
| mtip_ = bit; |
| } |
| |
| // Update the mtime register. |
| void RiscVClint::WriteMTimeLow(uint32_t value) { |
| mtime_ = (mtime_ & 0xffff'ffff'0000'0000ULL) | static_cast<uint64_t>(value); |
| } |
| |
| void RiscVClint::WriteMTimeHigh(uint32_t value) { |
| mtime_ = (mtime_ & 0xffff'ffffULL) | (static_cast<uint64_t>(value) << 32); |
| } |
| |
| } // namespace riscv |
| } // namespace sim |
| } // namespace mpact |