blob: 87bc7d74e15716381ac62118a9ea3ac897e422d5 [file] [log] [blame]
/*
* 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);
}
}