diff -r 92e679f7d248 -r df17c209342a configs/common/O3_ARM_v7a.py --- a/configs/common/O3_ARM_v7a.py Mon Nov 02 10:43:11 2015 +0000 +++ b/configs/common/O3_ARM_v7a.py Mon Nov 02 10:43:58 2015 +0000 @@ -185,6 +185,7 @@ assoc = 16 write_buffers = 8 prefetch_on_access = True + clusivity = 'mostly_excl' # Simple stride prefetcher prefetcher = StridePrefetcher(degree=8, latency = 1) tags = RandomRepl() diff -r 92e679f7d248 -r df17c209342a src/mem/cache/Cache.py --- a/src/mem/cache/Cache.py Mon Nov 02 10:43:11 2015 +0000 +++ b/src/mem/cache/Cache.py Mon Nov 02 10:43:58 2015 +0000 @@ -84,6 +84,22 @@ system = Param.System(Parent.any, "System we belong to") +# Enum for cache clusivity, currently mostly inclusive or mostly +# exclusive. +class Clusivity(Enum): vals = ['mostly_incl', 'mostly_excl'] + class Cache(BaseCache): type = 'Cache' cxx_header = 'mem/cache/cache.hh' + + # Control whether this cache should be mostly inclusive or mostly + # exclusive with respect to upstream caches. The behaviour on a + # fill is determined accordingly. For a mostly inclusive cache, + # blocks are allocated on all fill operations. Thus, L1 caches + # should be set as mostly inclusive even if they have no upstream + # caches. In the case of a mostly exclusive cache, fills are not + # allocating unless they came directly from a non-caching source, + # e.g. a table walker. Additionally, on a hit from an upstream + # cache a line is dropped for a mostly exclusive cache. + clusivity = Param.Clusivity('mostly_incl', + "Clusivity with upstream cache") diff -r 92e679f7d248 -r df17c209342a src/mem/cache/cache.hh --- a/src/mem/cache/cache.hh Mon Nov 02 10:43:11 2015 +0000 +++ b/src/mem/cache/cache.hh Mon Nov 02 10:43:58 2015 +0000 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2014 ARM Limited + * Copyright (c) 2012-2015 ARM Limited * All rights reserved. * * The license below extends only to copyright in the software and shall @@ -53,6 +53,7 @@ #define __MEM_CACHE_CACHE_HH__ #include "base/misc.hh" // fatal, panic, and warn +#include "enums/Clusivity.hh" #include "mem/cache/base.hh" #include "mem/cache/blk.hh" #include "mem/cache/mshr.hh" @@ -194,6 +195,13 @@ */ const bool prefetchOnAccess; + /** + * Clusivity with respect to the upstream cache, determining if we + * fill into both this cache and the cache above on a miss. Note + * that we currently do not support strict clusivity policies. + */ + const Enums::Clusivity clusivity; + /** * Upstream caches need this packet until true is returned, so * hold it for deletion until a subsequent call @@ -201,6 +209,35 @@ std::unique_ptr pendingDelete; /** + * Writebacks from the tempBlock, resulting on the response path + * in atomic mode, must happen after the call to recvAtomic has + * finished (for the right ordering of the packets). We therefore + * need to hold on to the packets, and have a method and an event + * to send them. + */ + PacketPtr tempBlockWriteback; + + /** + * Send the outstanding tempBlock writeback. To be called after + * recvAtomic finishes in cases where the block we filled is in + * fact the tempBlock, and now needs to be written back. + */ + void doTempBlockWriteback() { + assert(tempBlockWriteback != nullptr); + PacketList writebacks{tempBlockWriteback}; + doWritebacksAtomic(writebacks); + tempBlockWriteback = nullptr; + } + + /** + * An event to writeback the tempBlock after recvAtomic + * finishes. To avoid other calls to recvAtomic getting in + * between, we create this event with a higher priority. + */ + EventWrapper doTempBlockWritebackEvent; + + /** * Does all the processing necessary to perform the provided request. * @param pkt The memory request to perform. * @param blk The cache block to be updated. @@ -232,11 +269,34 @@ * @param pkt The memory request with the fill data. * @param blk The cache block if it already exists. * @param writebacks List for any writebacks that need to be performed. + * @param allocate Whether to allocate a block or use the temp block * @return Pointer to the new cache block. */ CacheBlk *handleFill(PacketPtr pkt, CacheBlk *blk, - PacketList &writebacks); + PacketList &writebacks, bool allocate); + /** + * Determine whether we should allocate on a fill or not. If this + * cache is mostly inclusive with regards to the upstream cache(s) + * we always allocate (for any non-forwarded and cacheable + * requests). In the case of a mostly exclusive cache, we + * allocate on fill if are dealing with a whole-line write (the + * latter behaves much like a writeback), if the original target + * packet came from a non-caching source, or if we are performing + * a prefetch or LLSC. + * + * @param cmd Command of the incoming requesting packet + * @return Whether we should allocate on the fill + */ + inline bool allocOnFill(MemCmd cmd) const + { + return clusivity == Enums::mostly_incl || + cmd == MemCmd::WriteLineReq || + cmd == MemCmd::ReadReq || + cmd == MemCmd::WriteReq || + cmd.isPrefetch() || + cmd.isLLSC(); + } /** * Performs the access specified by the request. diff -r 92e679f7d248 -r df17c209342a src/mem/cache/cache.cc --- a/src/mem/cache/cache.cc Mon Nov 02 10:43:11 2015 +0000 +++ b/src/mem/cache/cache.cc Mon Nov 02 10:43:58 2015 +0000 @@ -68,7 +68,10 @@ tags(p->tags), prefetcher(p->prefetcher), doFastWrites(true), - prefetchOnAccess(p->prefetch_on_access) + prefetchOnAccess(p->prefetch_on_access), + clusivity(p->clusivity), + tempBlockWriteback(nullptr), + doTempBlockWritebackEvent(this, false, EventBase::Delayed_Writeback_Pri) { tempBlock = new CacheBlk(); tempBlock->data = new uint8_t[blkSize]; @@ -198,7 +201,9 @@ if (blk->isDirty()) { pkt->assertMemInhibit(); } - // on ReadExReq we give up our copy unconditionally + // on ReadExReq we give up our copy unconditionally, + // even if this cache is mostly inclusive, we may want + // to revisit this if (blk != tempBlock) tags->invalidate(blk); blk->invalidate(); @@ -220,9 +225,32 @@ if (!deferred_response) { // if we are responding immediately and can // signal that we're transferring ownership - // along with exclusivity, do so + // (inhibit set) along with exclusivity + // (shared not set), do so pkt->assertMemInhibit(); + + // if this cache is mostly inclusive, we keep + // the block as writable (exclusive), and pass + // it upwards as writable and dirty + // (modified), hence we have multiple caches + // considering the same block writable, + // something that we get away with due to the + // fact that: 1) this cache has been + // considered the ordering points and + // responded to all snoops up till now, and 2) + // we always snoop upwards before consulting + // the local cache, both on a normal request + // (snooping done by the crossbar), and on a + // snoop blk->status &= ~BlkDirty; + + // if this cache is mostly exclusive with + // respect to the cache above, drop the block + if (clusivity == Enums::mostly_excl) { + if (blk != tempBlock) + tags->invalidate(blk); + blk->invalidate(); + } } else { // if we're responding after our own miss, // there's a window where the recipient didn't @@ -241,8 +269,11 @@ // Upgrade or Invalidate, since we have it Exclusively (E or // M), we ack then invalidate. assert(pkt->isUpgrade() || pkt->isInvalidate()); - assert(blk != tempBlock); - tags->invalidate(blk); + + // for invalidations we could be looking at the temp block + // (for upgrades we always allocate) + if (blk != tempBlock) + tags->invalidate(blk); blk->invalidate(); DPRINTF(Cache, "%s for %s addr %#llx size %d (invalidation)\n", __func__, pkt->cmdString(), pkt->getAddr(), pkt->getSize()); @@ -1027,13 +1058,15 @@ // write-line request to the cache that promoted // the write to a whole line - blk = handleFill(pkt, blk, writebacks); + blk = handleFill(pkt, blk, writebacks, + allocOnFill(pkt->cmd)); satisfyCpuSideRequest(pkt, blk); } else if (bus_pkt->isRead() || bus_pkt->cmd == MemCmd::UpgradeResp) { // we're updating cache state to allow us to // satisfy the upstream request from the cache - blk = handleFill(bus_pkt, blk, writebacks); + blk = handleFill(bus_pkt, blk, writebacks, + allocOnFill(pkt->cmd)); satisfyCpuSideRequest(pkt, blk); } else { // we're satisfying the upstream request without @@ -1056,9 +1089,32 @@ // immediately rather than calling requestMemSideBus() as we do // there). - // Handle writebacks (from the response handling) if needed + // do any writebacks resulting from the response handling doWritebacksAtomic(writebacks); + // if we used temp block, check to see if its valid and if so + // clear it out + if (blk == tempBlock && tempBlock->isValid()) { + // the atomic CPU calls recvAtomic for fetch and load/store + // sequentuially, and we may already have a tempBlock + // writeback from the fetch that we have not yet sent + if (tempBlockWriteback) { + // if that is the case, write the prevoius one back, and + // do not schedule any new event + doTempBlockWriteback(); + } else { + // the writeback/clean eviction happens after the call to + // recvAtomic has finished (but before any successive + // calls), so that the response handling from the fill is + // allowed to happen first + schedule(doTempBlockWritebackEvent, curTick()); + } + + tempBlockWriteback = blk->isDirty() ? writebackBlk(blk) : + cleanEvictBlk(blk); + blk->invalidate(); + } + if (pkt->needsResponse()) { pkt->makeAtomicResponse(); } @@ -1214,7 +1270,7 @@ DPRINTF(Cache, "Block for addr %#llx being updated in Cache\n", pkt->getAddr()); - blk = handleFill(pkt, blk, writebacks); + blk = handleFill(pkt, blk, writebacks, mshr->allocOnFill); assert(blk != NULL); } @@ -1258,7 +1314,7 @@ // deferred targets if possible mshr->promoteExclusive(); // NB: we use the original packet here and not the response! - blk = handleFill(tgt_pkt, blk, writebacks); + blk = handleFill(tgt_pkt, blk, writebacks, mshr->allocOnFill); assert(blk != NULL); // treat as a fill, and discard the invalidation @@ -1362,8 +1418,8 @@ // should not invalidate the block, so check if the // invalidation should be discarded if (is_invalidate || mshr->hasPostInvalidate()) { - assert(blk != tempBlock); - tags->invalidate(blk); + if (blk != tempBlock) + tags->invalidate(blk); blk->invalidate(); } else if (mshr->hasPostDowngrade()) { blk->status &= ~BlkWritable; @@ -1595,7 +1651,8 @@ // mode we don't mess with the write buffer (we just perform the // writebacks atomically once the original request is complete). CacheBlk* -Cache::handleFill(PacketPtr pkt, CacheBlk *blk, PacketList &writebacks) +Cache::handleFill(PacketPtr pkt, CacheBlk *blk, PacketList &writebacks, + bool allocate) { assert(pkt->isResponse() || pkt->cmd == MemCmd::WriteLineReq); Addr addr = pkt->getAddr(); @@ -1619,11 +1676,14 @@ // happens in the subsequent satisfyCpuSideRequest. assert(pkt->isRead() || pkt->cmd == MemCmd::WriteLineReq); - // need to do a replacement - blk = allocateBlock(addr, is_secure, writebacks); + // need to do a replacement if allocating, otherwise we stick + // with the temporary storage + blk = allocate ? allocateBlock(addr, is_secure, writebacks) : NULL; + if (blk == NULL) { - // No replaceable block... just use temporary storage to - // complete the current request and then get rid of it + // No replaceable block or a mostly exclusive + // cache... just use temporary storage to complete the + // current request and then get rid of it assert(!tempBlock->isValid()); blk = tempBlock; tempBlock->set = tags->extractSet(addr); @@ -1877,6 +1937,7 @@ // applies both to reads and writes and that for writes it // works thanks to the fact that we still have dirty data and // will write it back at a later point + assert(!pkt->memInhibitAsserted()); pkt->assertMemInhibit(); if (have_exclusive) { // in the case of an uncacheable request there is no point @@ -2284,6 +2345,11 @@ if (pkt->isWrite()) { pkt->setData(tgt_pkt->getConstPtr()); } + } else { + // here we are making a decision whether to allocate on + // fill or not, and possibly changing the decision as we + // go along, note that a positive decision is sticky + mshr->allocOnFill |= allocOnFill(tgt_pkt->cmd); } } diff -r 92e679f7d248 -r df17c209342a src/mem/cache/mshr.hh --- a/src/mem/cache/mshr.hh Mon Nov 02 10:43:11 2015 +0000 +++ b/src/mem/cache/mshr.hh Mon Nov 02 10:43:58 2015 +0000 @@ -161,6 +161,9 @@ /** True if the request is just a simple forward from an upper level */ bool isForward; + /** Keep track of whether we should allocate on fill or not */ + bool allocOnFill; + /** The pending* and post* flags are only valid if inService is * true. Using the accessor functions lets us detect if these * flags are accessed improperly. diff -r 92e679f7d248 -r df17c209342a src/mem/cache/mshr.cc --- a/src/mem/cache/mshr.cc Mon Nov 02 10:43:11 2015 +0000 +++ b/src/mem/cache/mshr.cc Mon Nov 02 10:43:58 2015 +0000 @@ -66,7 +66,8 @@ postInvalidate(false), postDowngrade(false), queue(NULL), order(0), blkAddr(0), blkSize(0), isSecure(false), inService(false), - isForward(false), threadNum(InvalidThreadID), data(NULL) + isForward(false), allocOnFill(false), + threadNum(InvalidThreadID), data(NULL) { } @@ -211,6 +212,7 @@ order = _order; assert(target); isForward = false; + allocOnFill = false; _isUncacheable = target->req->isUncacheable(); inService = false; downstreamPending = false; @@ -478,6 +480,7 @@ prefix, blkAddr, blkAddr + blkSize - 1, isSecure ? "s" : "ns", isForward ? "Forward" : "", + allocOnFill ? "AllocOnFill" : "", isForwardNoResponse() ? "ForwNoResp" : "", needsExclusive() ? "Excl" : "", _isUncacheable ? "Unc" : "",