mem-ruby: Fixed pipeline squashes caused by aliased requests

This patch was created by Bihn Pham during his internship at AMD.

This patch fixes a very significant performance bug when using the O3
CPU model and Ruby. The issue was Ruby returned false when it received
a request to the same address that already has an outstanding request or
when the memory is blocked. As a result, O3 unnecessary squashed the
pipeline and re-executed instructions. This fix merges readRequestTable
and writeRequestTable in Sequencer into a single request table that
keeps track of all requests and allows multiple outstanding requests to
the same address. This prevents O3 from squashing the pipeline.

Change-Id: If934d57b4736861e342de0ab18be4feec464273d
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/21219
Reviewed-by: Anthony Gutierrez <anthony.gutierrez@amd.com>
Maintainer: Anthony Gutierrez <anthony.gutierrez@amd.com>
Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
Joe Gross
2015-07-20 09:15:18 -05:00
committed by Anthony Gutierrez
parent 0125bf80b5
commit bb94296373
3 changed files with 212 additions and 236 deletions

View File

@@ -1,5 +1,6 @@
/*
* Copyright (c) 1999-2008 Mark D. Hill and David A. Wood
* Copyright (c) 2013 Advanced Micro Devices, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -87,39 +88,22 @@ Sequencer::wakeup()
// Check across all outstanding requests
int total_outstanding = 0;
RequestTable::iterator read = m_readRequestTable.begin();
RequestTable::iterator read_end = m_readRequestTable.end();
for (; read != read_end; ++read) {
SequencerRequest* request = read->second;
if (current_time - request->issue_time < m_deadlock_threshold)
continue;
for (const auto &table_entry : m_RequestTable) {
for (const auto seq_req : table_entry.second) {
if (current_time - seq_req.issue_time < m_deadlock_threshold)
continue;
panic("Possible Deadlock detected. Aborting!\n"
"version: %d request.paddr: 0x%x m_readRequestTable: %d "
"current time: %u issue_time: %d difference: %d\n", m_version,
request->pkt->getAddr(), m_readRequestTable.size(),
current_time * clockPeriod(), request->issue_time * clockPeriod(),
(current_time * clockPeriod()) - (request->issue_time * clockPeriod()));
panic("Possible Deadlock detected. Aborting!\n version: %d "
"request.paddr: 0x%x m_readRequestTable: %d current time: "
"%u issue_time: %d difference: %d\n", m_version,
seq_req.pkt->getAddr(), table_entry.second.size(),
current_time * clockPeriod(), seq_req.issue_time
* clockPeriod(), (current_time * clockPeriod())
- (seq_req.issue_time * clockPeriod()));
}
total_outstanding += table_entry.second.size();
}
RequestTable::iterator write = m_writeRequestTable.begin();
RequestTable::iterator write_end = m_writeRequestTable.end();
for (; write != write_end; ++write) {
SequencerRequest* request = write->second;
if (current_time - request->issue_time < m_deadlock_threshold)
continue;
panic("Possible Deadlock detected. Aborting!\n"
"version: %d request.paddr: 0x%x m_writeRequestTable: %d "
"current time: %u issue_time: %d difference: %d\n", m_version,
request->pkt->getAddr(), m_writeRequestTable.size(),
current_time * clockPeriod(), request->issue_time * clockPeriod(),
(current_time * clockPeriod()) - (request->issue_time * clockPeriod()));
}
total_outstanding += m_writeRequestTable.size();
total_outstanding += m_readRequestTable.size();
assert(m_outstanding_count == total_outstanding);
if (m_outstanding_count > 0) {
@@ -156,14 +140,12 @@ void Sequencer::resetStats()
}
}
// Insert the request on the correct request table. Return true if
// the entry was already present.
// Insert the request in the request table. Return RequestStatus_Aliased
// if the entry was already present.
RequestStatus
Sequencer::insertRequest(PacketPtr pkt, RubyRequestType request_type)
Sequencer::insertRequest(PacketPtr pkt, RubyRequestType primary_type,
RubyRequestType secondary_type)
{
assert(m_outstanding_count ==
(m_writeRequestTable.size() + m_readRequestTable.size()));
// See if we should schedule a deadlock check
if (!deadlockCheckEvent.scheduled() &&
drainState() != DrainState::Draining) {
@@ -171,73 +153,17 @@ Sequencer::insertRequest(PacketPtr pkt, RubyRequestType request_type)
}
Addr line_addr = makeLineAddress(pkt->getAddr());
// Check if there is any outstanding request for the same cache line.
auto &seq_req_list = m_RequestTable[line_addr];
// Create a default entry
seq_req_list.emplace_back(pkt, primary_type, secondary_type, curCycle());
m_outstanding_count++;
// Check if the line is blocked for a Locked_RMW
if (m_controller->isBlocked(line_addr) &&
(request_type != RubyRequestType_Locked_RMW_Write)) {
// Return that this request's cache line address aliases with
// a prior request that locked the cache line. The request cannot
// proceed until the cache line is unlocked by a Locked_RMW_Write
if (seq_req_list.size() > 1) {
return RequestStatus_Aliased;
}
// Create a default entry, mapping the address to NULL, the cast is
// there to make gcc 4.4 happy
RequestTable::value_type default_entry(line_addr,
(SequencerRequest*) NULL);
if ((request_type == RubyRequestType_ST) ||
(request_type == RubyRequestType_RMW_Read) ||
(request_type == RubyRequestType_RMW_Write) ||
(request_type == RubyRequestType_Load_Linked) ||
(request_type == RubyRequestType_Store_Conditional) ||
(request_type == RubyRequestType_Locked_RMW_Read) ||
(request_type == RubyRequestType_Locked_RMW_Write) ||
(request_type == RubyRequestType_FLUSH)) {
// Check if there is any outstanding read request for the same
// cache line.
if (m_readRequestTable.count(line_addr) > 0) {
m_store_waiting_on_load++;
return RequestStatus_Aliased;
}
pair<RequestTable::iterator, bool> r =
m_writeRequestTable.insert(default_entry);
if (r.second) {
RequestTable::iterator i = r.first;
i->second = new SequencerRequest(pkt, request_type, curCycle());
m_outstanding_count++;
} else {
// There is an outstanding write request for the cache line
m_store_waiting_on_store++;
return RequestStatus_Aliased;
}
} else {
// Check if there is any outstanding write request for the same
// cache line.
if (m_writeRequestTable.count(line_addr) > 0) {
m_load_waiting_on_store++;
return RequestStatus_Aliased;
}
pair<RequestTable::iterator, bool> r =
m_readRequestTable.insert(default_entry);
if (r.second) {
RequestTable::iterator i = r.first;
i->second = new SequencerRequest(pkt, request_type, curCycle());
m_outstanding_count++;
} else {
// There is an outstanding read request for the cache line
m_load_waiting_on_load++;
return RequestStatus_Aliased;
}
}
m_outstandReqHist.sample(m_outstanding_count);
assert(m_outstanding_count ==
(m_writeRequestTable.size() + m_readRequestTable.size()));
return RequestStatus_Ready;
}
@@ -246,8 +172,6 @@ void
Sequencer::markRemoved()
{
m_outstanding_count--;
assert(m_outstanding_count ==
m_writeRequestTable.size() + m_readRequestTable.size());
}
void
@@ -307,48 +231,67 @@ Sequencer::handleLlsc(Addr address, SequencerRequest* request)
}
void
Sequencer::recordMissLatency(const Cycles cycles, const RubyRequestType type,
Sequencer::recordMissLatency(SequencerRequest* srequest, bool llscSuccess,
const MachineType respondingMach,
bool isExternalHit, Cycles issuedTime,
Cycles initialRequestTime,
bool isExternalHit, Cycles initialRequestTime,
Cycles forwardRequestTime,
Cycles firstResponseTime, Cycles completionTime)
Cycles firstResponseTime)
{
m_latencyHist.sample(cycles);
m_typeLatencyHist[type]->sample(cycles);
RubyRequestType type = srequest->m_type;
Cycles issued_time = srequest->issue_time;
Cycles completion_time = curCycle();
assert(curCycle() >= issued_time);
Cycles total_lat = completion_time - issued_time;
if (initialRequestTime < issued_time) {
// if the request was combined in the protocol with an earlier request
// for the same address, it is possible that it will return an
// initialRequestTime corresponding the earlier request. Since Cycles
// is unsigned, we can't let this request get profiled below.
total_lat = Cycles(0);
}
DPRINTFR(ProtocolTrace, "%15s %3s %10s%20s %6s>%-6s %s %d cycles\n",
curTick(), m_version, "Seq", llscSuccess ? "Done" : "SC_Failed",
"", "", printAddress(srequest->pkt->getAddr()), total_lat);
m_latencyHist.sample(total_lat);
m_typeLatencyHist[type]->sample(total_lat);
if (isExternalHit) {
m_missLatencyHist.sample(cycles);
m_missTypeLatencyHist[type]->sample(cycles);
m_missLatencyHist.sample(total_lat);
m_missTypeLatencyHist[type]->sample(total_lat);
if (respondingMach != MachineType_NUM) {
m_missMachLatencyHist[respondingMach]->sample(cycles);
m_missTypeMachLatencyHist[type][respondingMach]->sample(cycles);
m_missMachLatencyHist[respondingMach]->sample(total_lat);
m_missTypeMachLatencyHist[type][respondingMach]->sample(total_lat);
if ((issuedTime <= initialRequestTime) &&
if ((issued_time <= initialRequestTime) &&
(initialRequestTime <= forwardRequestTime) &&
(forwardRequestTime <= firstResponseTime) &&
(firstResponseTime <= completionTime)) {
(firstResponseTime <= completion_time)) {
m_IssueToInitialDelayHist[respondingMach]->sample(
initialRequestTime - issuedTime);
initialRequestTime - issued_time);
m_InitialToForwardDelayHist[respondingMach]->sample(
forwardRequestTime - initialRequestTime);
m_ForwardToFirstResponseDelayHist[respondingMach]->sample(
firstResponseTime - forwardRequestTime);
m_FirstResponseToCompletionDelayHist[respondingMach]->sample(
completionTime - firstResponseTime);
completion_time - firstResponseTime);
} else {
m_IncompleteTimes[respondingMach]++;
}
}
} else {
m_hitLatencyHist.sample(cycles);
m_hitTypeLatencyHist[type]->sample(cycles);
m_hitLatencyHist.sample(total_lat);
m_hitTypeLatencyHist[type]->sample(total_lat);
if (respondingMach != MachineType_NUM) {
m_hitMachLatencyHist[respondingMach]->sample(cycles);
m_hitTypeMachLatencyHist[type][respondingMach]->sample(cycles);
m_hitMachLatencyHist[respondingMach]->sample(total_lat);
m_hitTypeMachLatencyHist[type][respondingMach]->sample(total_lat);
}
}
}
@@ -360,52 +303,82 @@ Sequencer::writeCallback(Addr address, DataBlock& data,
const Cycles forwardRequestTime,
const Cycles firstResponseTime)
{
//
// Free the whole list as we assume we have had the exclusive access
// to this cache line when response for the write comes back
//
assert(address == makeLineAddress(address));
assert(m_writeRequestTable.count(makeLineAddress(address)));
assert(m_RequestTable.find(address) != m_RequestTable.end());
auto &seq_req_list = m_RequestTable[address];
RequestTable::iterator i = m_writeRequestTable.find(address);
assert(i != m_writeRequestTable.end());
SequencerRequest* request = i->second;
// Perform hitCallback on every cpu request made to this cache block while
// ruby request was outstanding. Since only 1 ruby request was made,
// profile the ruby latency once.
bool ruby_request = true;
int aliased_stores = 0;
int aliased_loads = 0;
while (!seq_req_list.empty()) {
SequencerRequest &seq_req = seq_req_list.front();
if (ruby_request) {
assert(seq_req.m_type != RubyRequestType_LD);
assert(seq_req.m_type != RubyRequestType_IFETCH);
}
m_writeRequestTable.erase(i);
markRemoved();
// handle write request
if ((seq_req.m_type != RubyRequestType_LD) &&
(seq_req.m_type != RubyRequestType_IFETCH)) {
//
// For Alpha, properly handle LL, SC, and write requests with
// respect to locked cache blocks.
//
// Not valid for Garnet_standalone protocl
//
bool success = true;
if (!m_runningGarnetStandalone)
success = handleLlsc(address, &seq_req);
assert((request->m_type == RubyRequestType_ST) ||
(request->m_type == RubyRequestType_ATOMIC) ||
(request->m_type == RubyRequestType_RMW_Read) ||
(request->m_type == RubyRequestType_RMW_Write) ||
(request->m_type == RubyRequestType_Load_Linked) ||
(request->m_type == RubyRequestType_Store_Conditional) ||
(request->m_type == RubyRequestType_Locked_RMW_Read) ||
(request->m_type == RubyRequestType_Locked_RMW_Write) ||
(request->m_type == RubyRequestType_FLUSH));
// Handle SLICC block_on behavior for Locked_RMW accesses. NOTE: the
// address variable here is assumed to be a line address, so when
// blocking buffers, must check line addresses.
if (seq_req.m_type == RubyRequestType_Locked_RMW_Read) {
// blockOnQueue blocks all first-level cache controller queues
// waiting on memory accesses for the specified address that go
// to the specified queue. In this case, a Locked_RMW_Write must
// go to the mandatory_q before unblocking the first-level
// controller. This will block standard loads, stores, ifetches,
// etc.
m_controller->blockOnQueue(address, m_mandatory_q_ptr);
} else if (seq_req.m_type == RubyRequestType_Locked_RMW_Write) {
m_controller->unblock(address);
}
//
// For Alpha, properly handle LL, SC, and write requests with respect to
// locked cache blocks.
//
// Not valid for Garnet_standalone protocl
//
bool success = true;
if (!m_runningGarnetStandalone)
success = handleLlsc(address, request);
// Handle SLICC block_on behavior for Locked_RMW accesses. NOTE: the
// address variable here is assumed to be a line address, so when
// blocking buffers, must check line addresses.
if (request->m_type == RubyRequestType_Locked_RMW_Read) {
// blockOnQueue blocks all first-level cache controller queues
// waiting on memory accesses for the specified address that go to
// the specified queue. In this case, a Locked_RMW_Write must go to
// the mandatory_q before unblocking the first-level controller.
// This will block standard loads, stores, ifetches, etc.
m_controller->blockOnQueue(address, m_mandatory_q_ptr);
} else if (request->m_type == RubyRequestType_Locked_RMW_Write) {
m_controller->unblock(address);
if (ruby_request) {
recordMissLatency(&seq_req, success, mach, externalHit,
initialRequestTime, forwardRequestTime,
firstResponseTime);
} else {
aliased_stores++;
}
hitCallback(&seq_req, data, success, mach, externalHit,
initialRequestTime, forwardRequestTime,
firstResponseTime);
} else {
// handle read request
assert(!ruby_request);
aliased_loads++;
hitCallback(&seq_req, data, true, mach, externalHit,
initialRequestTime, forwardRequestTime,
firstResponseTime);
}
seq_req_list.pop_front();
markRemoved();
ruby_request = false;
}
hitCallback(request, data, success, mach, externalHit,
initialRequestTime, forwardRequestTime, firstResponseTime);
// free all outstanding requests corresponding to this address
if (seq_req_list.empty()) {
m_RequestTable.erase(address);
}
}
void
@@ -415,21 +388,50 @@ Sequencer::readCallback(Addr address, DataBlock& data,
Cycles forwardRequestTime,
Cycles firstResponseTime)
{
//
// Free up read requests until we hit the first Write request
// or end of the corresponding list.
//
assert(address == makeLineAddress(address));
assert(m_readRequestTable.count(makeLineAddress(address)));
assert(m_RequestTable.find(address) != m_RequestTable.end());
auto &seq_req_list = m_RequestTable[address];
RequestTable::iterator i = m_readRequestTable.find(address);
assert(i != m_readRequestTable.end());
SequencerRequest* request = i->second;
// Perform hitCallback on every cpu request made to this cache block while
// ruby request was outstanding. Since only 1 ruby request was made,
// profile the ruby latency once.
bool ruby_request = true;
int aliased_loads = 0;
while (!seq_req_list.empty()) {
SequencerRequest &seq_req = seq_req_list.front();
if (ruby_request) {
assert((seq_req.m_type == RubyRequestType_LD) ||
(seq_req.m_type == RubyRequestType_IFETCH));
} else {
aliased_loads++;
}
if ((seq_req.m_type != RubyRequestType_LD) &&
(seq_req.m_type != RubyRequestType_IFETCH)) {
// Write request: reissue request to the cache hierarchy
issueRequest(seq_req.pkt, seq_req.m_second_type);
break;
}
if (ruby_request) {
recordMissLatency(&seq_req, true, mach, externalHit,
initialRequestTime, forwardRequestTime,
firstResponseTime);
}
hitCallback(&seq_req, data, true, mach, externalHit,
initialRequestTime, forwardRequestTime,
firstResponseTime);
seq_req_list.pop_front();
markRemoved();
ruby_request = false;
}
m_readRequestTable.erase(i);
markRemoved();
assert((request->m_type == RubyRequestType_LD) ||
(request->m_type == RubyRequestType_IFETCH));
hitCallback(request, data, true, mach, externalHit,
initialRequestTime, forwardRequestTime, firstResponseTime);
// free all outstanding requests corresponding to this address
if (seq_req_list.empty()) {
m_RequestTable.erase(address);
}
}
void
@@ -447,20 +449,6 @@ Sequencer::hitCallback(SequencerRequest* srequest, DataBlock& data,
PacketPtr pkt = srequest->pkt;
Addr request_address(pkt->getAddr());
RubyRequestType type = srequest->m_type;
Cycles issued_time = srequest->issue_time;
assert(curCycle() >= issued_time);
Cycles total_latency = curCycle() - issued_time;
// Profile the latency for all demand accesses.
recordMissLatency(total_latency, type, mach, externalHit, issued_time,
initialRequestTime, forwardRequestTime,
firstResponseTime, curCycle());
DPRINTFR(ProtocolTrace, "%15s %3s %10s%20s %6s>%-6s %#x %d cycles\n",
curTick(), m_version, "Seq",
llscSuccess ? "Done" : "SC_Failed", "", "",
printAddress(request_address), total_latency);
// update the data unless it is a non-data-carrying flush
if (RubySystem::getWarmupEnabled()) {
@@ -504,8 +492,6 @@ Sequencer::hitCallback(SequencerRequest* srequest, DataBlock& data,
testerSenderState->subBlock.mergeFrom(data);
}
delete srequest;
RubySystem *rs = m_ruby_system;
if (RubySystem::getWarmupEnabled()) {
assert(pkt->req);
@@ -523,7 +509,7 @@ Sequencer::hitCallback(SequencerRequest* srequest, DataBlock& data,
bool
Sequencer::empty() const
{
return m_writeRequestTable.empty() && m_readRequestTable.empty();
return m_RequestTable.empty();
}
RequestStatus
@@ -606,11 +592,24 @@ Sequencer::makeRequest(PacketPtr pkt)
}
}
RequestStatus status = insertRequest(pkt, primary_type);
if (status != RequestStatus_Ready)
return status;
// Check if the line is blocked for a Locked_RMW
if (m_controller->isBlocked(makeLineAddress(pkt->getAddr())) &&
(primary_type != RubyRequestType_Locked_RMW_Write)) {
// Return that this request's cache line address aliases with
// a prior request that locked the cache line. The request cannot
// proceed until the cache line is unlocked by a Locked_RMW_Write
return RequestStatus_Aliased;
}
issueRequest(pkt, secondary_type);
RequestStatus status = insertRequest(pkt, primary_type, secondary_type);
// It is OK to receive RequestStatus_Aliased, it can be considered Issued
if (status != RequestStatus_Ready && status != RequestStatus_Aliased)
return status;
// non-aliased with any existing request in the request table, just issue
// to the cache
if (status != RequestStatus_Aliased)
issueRequest(pkt, secondary_type);
// TODO: issue hardware prefetches here
return RequestStatus_Issued;
@@ -658,12 +657,12 @@ template <class KEY, class VALUE>
std::ostream &
operator<<(ostream &out, const std::unordered_map<KEY, VALUE> &map)
{
auto i = map.begin();
auto end = map.end();
out << "[";
for (; i != end; ++i)
out << " " << i->first << "=" << i->second;
for (const auto &table_entry : map) {
out << "[ " << table_entry.first << " =";
for (const auto &seq_req : table_entry.second) {
out << " " << RubyRequestType_to_string(seq_req.m_second_type);
}
}
out << " ]";
return out;
@@ -674,8 +673,7 @@ Sequencer::print(ostream& out) const
{
out << "[Sequencer: " << m_version
<< ", outstanding requests: " << m_outstanding_count
<< ", read request table: " << m_readRequestTable
<< ", write request table: " << m_writeRequestTable
<< ", request table: " << m_RequestTable
<< "]";
}
@@ -693,7 +691,6 @@ Sequencer::recordRequestType(SequencerRequestType requestType) {
SequencerRequestType_to_string(requestType));
}
void
Sequencer::evictionCallback(Addr address)
{
@@ -705,23 +702,6 @@ Sequencer::regStats()
{
RubyPort::regStats();
m_store_waiting_on_load
.name(name() + ".store_waiting_on_load")
.desc("Number of times a store aliased with a pending load")
.flags(Stats::nozero);
m_store_waiting_on_store
.name(name() + ".store_waiting_on_store")
.desc("Number of times a store aliased with a pending store")
.flags(Stats::nozero);
m_load_waiting_on_load
.name(name() + ".load_waiting_on_load")
.desc("Number of times a load aliased with a pending load")
.flags(Stats::nozero);
m_load_waiting_on_store
.name(name() + ".load_waiting_on_store")
.desc("Number of times a load aliased with a pending store")
.flags(Stats::nozero);
// These statistical variables are not for display.
// The profiler will collate these across different
// sequencers and display those collated statistics.

View File

@@ -30,6 +30,7 @@
#define __MEM_RUBY_SYSTEM_SEQUENCER_HH__
#include <iostream>
#include <list>
#include <unordered_map>
#include "mem/ruby/common/Address.hh"
@@ -44,11 +45,12 @@ struct SequencerRequest
{
PacketPtr pkt;
RubyRequestType m_type;
RubyRequestType m_second_type;
Cycles issue_time;
SequencerRequest(PacketPtr _pkt, RubyRequestType _m_type,
Cycles _issue_time)
: pkt(_pkt), m_type(_m_type), issue_time(_issue_time)
RubyRequestType _m_second_type, Cycles _issue_time)
: pkt(_pkt), m_type(_m_type), m_second_type(_m_second_type),
issue_time(_issue_time)
{}
};
@@ -151,21 +153,21 @@ class Sequencer : public RubyPort
private:
void issueRequest(PacketPtr pkt, RubyRequestType type);
void hitCallback(SequencerRequest* request, DataBlock& data,
void hitCallback(SequencerRequest* srequest, DataBlock& data,
bool llscSuccess,
const MachineType mach, const bool externalHit,
const Cycles initialRequestTime,
const Cycles forwardRequestTime,
const Cycles firstResponseTime);
void recordMissLatency(const Cycles t, const RubyRequestType type,
void recordMissLatency(SequencerRequest* srequest, bool llscSuccess,
const MachineType respondingMach,
bool isExternalHit, Cycles issuedTime,
Cycles initialRequestTime,
Cycles forwardRequestTime, Cycles firstResponseTime,
Cycles completionTime);
bool isExternalHit, Cycles initialRequestTime,
Cycles forwardRequestTime,
Cycles firstResponseTime);
RequestStatus insertRequest(PacketPtr pkt, RubyRequestType request_type);
RequestStatus insertRequest(PacketPtr pkt, RubyRequestType primary_type,
RubyRequestType secondary_type);
bool handleLlsc(Addr address, SequencerRequest* request);
// Private copy constructor and assignment operator
@@ -186,19 +188,13 @@ class Sequencer : public RubyPort
Cycles m_data_cache_hit_latency;
Cycles m_inst_cache_hit_latency;
typedef std::unordered_map<Addr, SequencerRequest*> RequestTable;
RequestTable m_writeRequestTable;
RequestTable m_readRequestTable;
// RequestTable contains both read and write requests, handles aliasing
std::unordered_map<Addr, std::list<SequencerRequest>> m_RequestTable;
// Global outstanding request count, across all request tables
int m_outstanding_count;
bool m_deadlock_check_scheduled;
//! Counters for recording aliasing information.
Stats::Scalar m_store_waiting_on_load;
Stats::Scalar m_store_waiting_on_store;
Stats::Scalar m_load_waiting_on_store;
Stats::Scalar m_load_waiting_on_load;
int m_coreId;
bool m_runningGarnetStandalone;

View File

@@ -4,4 +4,4 @@ gem5 is copyrighted software; use the --copyright option for details.
Global frequency set at 1000000000 ticks per second
Beginning simulation!
Exiting @ tick 9831 because Ruby Tester completed
Exiting @ tick 9981 because Ruby Tester completed