| /* |
| * Copyright (c) 2020 ARM Limited |
| * All rights reserved |
| * |
| * The license below extends only to copyright in the software and shall |
| * not be construed as granting a license to any other intellectual |
| * property including but not limited to intellectual property relating |
| * to a hardware implementation of the functionality of the software |
| * licensed hereunder. You may use the software subject to the license |
| * terms below provided that you ensure that this notice is replicated |
| * unmodified and in its entirety in all distributions of the software, |
| * modified or unmodified, in source code or in binary form. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer; |
| * redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution; |
| * neither the name of the copyright holders nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "mem/ruby/system/HTMSequencer.hh" |
| |
| #include "debug/HtmMem.hh" |
| #include "debug/RubyPort.hh" |
| #include "mem/ruby/slicc_interface/RubySlicc_Util.hh" |
| #include "sim/system.hh" |
| |
| using namespace std; |
| |
| HtmCacheFailure |
| HTMSequencer::htmRetCodeConversion( |
| const HtmFailedInCacheReason ruby_ret_code) |
| { |
| switch (ruby_ret_code) { |
| case HtmFailedInCacheReason_NO_FAIL: |
| return HtmCacheFailure::NO_FAIL; |
| case HtmFailedInCacheReason_FAIL_SELF: |
| return HtmCacheFailure::FAIL_SELF; |
| case HtmFailedInCacheReason_FAIL_REMOTE: |
| return HtmCacheFailure::FAIL_REMOTE; |
| case HtmFailedInCacheReason_FAIL_OTHER: |
| return HtmCacheFailure::FAIL_OTHER; |
| default: |
| panic("Invalid htm return code\n"); |
| } |
| } |
| |
| HTMSequencer * |
| RubyHTMSequencerParams::create() |
| { |
| return new HTMSequencer(this); |
| } |
| |
| HTMSequencer::HTMSequencer(const RubyHTMSequencerParams *p) |
| : Sequencer(p) |
| { |
| m_htmstart_tick = 0; |
| m_htmstart_instruction = 0; |
| } |
| |
| HTMSequencer::~HTMSequencer() |
| { |
| } |
| |
| void |
| HTMSequencer::htmCallback(Addr address, |
| const HtmCallbackMode mode, |
| const HtmFailedInCacheReason htm_return_code) |
| { |
| // mode=0: HTM command |
| // mode=1: transaction failed - inform via LD |
| // mode=2: transaction failed - inform via ST |
| |
| if (mode == HtmCallbackMode_HTM_CMD) { |
| SequencerRequest* request = nullptr; |
| |
| assert(m_htmCmdRequestTable.size() > 0); |
| |
| request = m_htmCmdRequestTable.front(); |
| m_htmCmdRequestTable.pop_front(); |
| |
| assert(isHtmCmdRequest(request->m_type)); |
| |
| PacketPtr pkt = request->pkt; |
| delete request; |
| |
| // valid responses have zero as the payload |
| uint8_t* dataptr = pkt->getPtr<uint8_t>(); |
| memset(dataptr, 0, pkt->getSize()); |
| *dataptr = (uint8_t) htm_return_code; |
| |
| // record stats |
| if (htm_return_code == HtmFailedInCacheReason_NO_FAIL) { |
| if (pkt->req->isHTMStart()) { |
| m_htmstart_tick = pkt->req->time(); |
| m_htmstart_instruction = pkt->req->getInstCount(); |
| DPRINTF(HtmMem, "htmStart - htmUid=%u\n", |
| pkt->getHtmTransactionUid()); |
| } else if (pkt->req->isHTMCommit()) { |
| Tick transaction_ticks = pkt->req->time() - m_htmstart_tick; |
| Cycles transaction_cycles = ticksToCycles(transaction_ticks); |
| m_htm_transaction_cycles.sample(transaction_cycles); |
| m_htmstart_tick = 0; |
| Counter transaction_instructions = |
| pkt->req->getInstCount() - m_htmstart_instruction; |
| m_htm_transaction_instructions.sample( |
| transaction_instructions); |
| m_htmstart_instruction = 0; |
| DPRINTF(HtmMem, "htmCommit - htmUid=%u\n", |
| pkt->getHtmTransactionUid()); |
| } else if (pkt->req->isHTMAbort()) { |
| HtmFailureFaultCause cause = pkt->req->getHtmAbortCause(); |
| assert(cause != HtmFailureFaultCause::INVALID); |
| auto cause_idx = static_cast<int>(cause); |
| m_htm_transaction_abort_cause[cause_idx]++; |
| DPRINTF(HtmMem, "htmAbort - reason=%s - htmUid=%u\n", |
| htmFailureToStr(cause), |
| pkt->getHtmTransactionUid()); |
| } |
| } else { |
| DPRINTF(HtmMem, "HTM_CMD: fail - htmUid=%u\n", |
| pkt->getHtmTransactionUid()); |
| } |
| |
| rubyHtmCallback(pkt, htm_return_code); |
| testDrainComplete(); |
| } else if (mode == HtmCallbackMode_LD_FAIL || |
| mode == HtmCallbackMode_ST_FAIL) { |
| // transaction failed |
| assert(address == makeLineAddress(address)); |
| assert(m_RequestTable.find(address) != m_RequestTable.end()); |
| |
| auto &seq_req_list = m_RequestTable[address]; |
| while (!seq_req_list.empty()) { |
| SequencerRequest &request = seq_req_list.front(); |
| |
| PacketPtr pkt = request.pkt; |
| markRemoved(); |
| |
| // TODO - atomics |
| |
| // store conditionals should indicate failure |
| if (request.m_type == RubyRequestType_Store_Conditional) { |
| pkt->req->setExtraData(0); |
| } |
| |
| DPRINTF(HtmMem, "%s_FAIL: size=%d - " |
| "addr=0x%lx - htmUid=%d\n", |
| (mode == HtmCallbackMode_LD_FAIL) ? "LD" : "ST", |
| pkt->getSize(), |
| address, pkt->getHtmTransactionUid()); |
| |
| rubyHtmCallback(pkt, htm_return_code); |
| testDrainComplete(); |
| pkt = nullptr; |
| seq_req_list.pop_front(); |
| } |
| // free all outstanding requests corresponding to this address |
| if (seq_req_list.empty()) { |
| m_RequestTable.erase(address); |
| } |
| } else { |
| panic("unrecognised HTM callback mode\n"); |
| } |
| } |
| |
| void |
| HTMSequencer::regStats() |
| { |
| Sequencer::regStats(); |
| |
| // hardware transactional memory |
| m_htm_transaction_cycles |
| .init(10) |
| .name(name() + ".htm_transaction_cycles") |
| .desc("number of cycles spent in an outer transaction") |
| .flags(Stats::pdf | Stats::dist | Stats::nozero | Stats::nonan) |
| ; |
| m_htm_transaction_instructions |
| .init(10) |
| .name(name() + ".htm_transaction_instructions") |
| .desc("number of instructions spent in an outer transaction") |
| .flags(Stats::pdf | Stats::dist | Stats::nozero | Stats::nonan) |
| ; |
| auto num_causes = static_cast<int>(HtmFailureFaultCause::NUM_CAUSES); |
| m_htm_transaction_abort_cause |
| .init(num_causes) |
| .name(name() + ".htm_transaction_abort_cause") |
| .desc("cause of htm transaction abort") |
| .flags(Stats::total | Stats::pdf | Stats::dist | Stats::nozero) |
| ; |
| |
| for (unsigned cause_idx = 0; cause_idx < num_causes; ++cause_idx) { |
| m_htm_transaction_abort_cause.subname( |
| cause_idx, |
| htmFailureToStr(HtmFailureFaultCause(cause_idx))); |
| } |
| } |
| |
| void |
| HTMSequencer::rubyHtmCallback(PacketPtr pkt, |
| const HtmFailedInCacheReason htm_return_code) |
| { |
| // The packet was destined for memory and has not yet been turned |
| // into a response |
| assert(system->isMemAddr(pkt->getAddr()) || system->isDeviceMemAddr(pkt)); |
| assert(pkt->isRequest()); |
| |
| // First retrieve the request port from the sender State |
| RubyPort::SenderState *senderState = |
| safe_cast<RubyPort::SenderState *>(pkt->popSenderState()); |
| |
| MemResponsePort *port = safe_cast<MemResponsePort*>(senderState->port); |
| assert(port != nullptr); |
| delete senderState; |
| |
| //port->htmCallback(pkt, htm_return_code); |
| DPRINTF(HtmMem, "HTM callback: start=%d, commit=%d, " |
| "cancel=%d, rc=%d\n", |
| pkt->req->isHTMStart(), pkt->req->isHTMCommit(), |
| pkt->req->isHTMCancel(), htm_return_code); |
| |
| // turn packet around to go back to requestor if response expected |
| if (pkt->needsResponse()) { |
| DPRINTF(RubyPort, "Sending packet back over port\n"); |
| pkt->makeHtmTransactionalReqResponse( |
| htmRetCodeConversion(htm_return_code)); |
| port->schedTimingResp(pkt, curTick()); |
| } else { |
| delete pkt; |
| } |
| |
| trySendRetries(); |
| } |
| |
| void |
| HTMSequencer::wakeup() |
| { |
| Sequencer::wakeup(); |
| |
| // Check for deadlock of any of the requests |
| Cycles current_time = curCycle(); |
| |
| // hardware transactional memory commands |
| std::deque<SequencerRequest*>::iterator htm = |
| m_htmCmdRequestTable.begin(); |
| std::deque<SequencerRequest*>::iterator htm_end = |
| m_htmCmdRequestTable.end(); |
| |
| for (; htm != htm_end; ++htm) { |
| SequencerRequest* request = *htm; |
| if (current_time - request->issue_time < m_deadlock_threshold) |
| continue; |
| |
| panic("Possible Deadlock detected. Aborting!\n" |
| "version: %d m_htmCmdRequestTable: %d " |
| "current time: %u issue_time: %d difference: %d\n", |
| m_version, m_htmCmdRequestTable.size(), |
| current_time * clockPeriod(), |
| request->issue_time * clockPeriod(), |
| (current_time * clockPeriod()) - |
| (request->issue_time * clockPeriod())); |
| } |
| } |
| |
| bool |
| HTMSequencer::empty() const |
| { |
| return Sequencer::empty() && m_htmCmdRequestTable.empty(); |
| } |
| |
| template <class VALUE> |
| std::ostream & |
| operator<<(ostream &out, const std::deque<VALUE> &queue) |
| { |
| auto i = queue.begin(); |
| auto end = queue.end(); |
| |
| out << "["; |
| for (; i != end; ++i) |
| out << " " << *i; |
| out << " ]"; |
| |
| return out; |
| } |
| |
| void |
| HTMSequencer::print(ostream& out) const |
| { |
| Sequencer::print(out); |
| |
| out << "+ [HTMSequencer: " << m_version |
| << ", htm cmd request table: " << m_htmCmdRequestTable |
| << "]"; |
| } |
| |
| // Insert the request in the request table. Return RequestStatus_Aliased |
| // if the entry was already present. |
| RequestStatus |
| HTMSequencer::insertRequest(PacketPtr pkt, RubyRequestType primary_type, |
| RubyRequestType secondary_type) |
| { |
| if (isHtmCmdRequest(primary_type)) { |
| // for the moment, allow just one HTM cmd into the cache controller. |
| // Later this can be adjusted for optimization, e.g. |
| // back-to-back HTM_Starts. |
| if ((m_htmCmdRequestTable.size() > 0) && !pkt->req->isHTMAbort()) |
| return RequestStatus_BufferFull; |
| |
| // insert request into HtmCmd queue |
| SequencerRequest* htmReq = |
| new SequencerRequest(pkt, primary_type, secondary_type, |
| curCycle()); |
| assert(htmReq); |
| m_htmCmdRequestTable.push_back(htmReq); |
| return RequestStatus_Ready; |
| } else { |
| return Sequencer::insertRequest(pkt, primary_type, secondary_type); |
| } |
| } |