From 893533a1264bb369b47f74493adf30ce22829f34 Mon Sep 17 00:00:00 2001 From: Andreas Hansson Date: Fri, 3 Jul 2015 10:14:39 -0400 Subject: [PATCH] mem: Allow read-only caches and check compliance This patch adds a parameter to the BaseCache to enable a read-only cache, for example for the instruction cache, or table-walker cache (not for x86). A number of checks are put in place in the code to ensure a read-only cache does not end up with dirty data. A follow-on patch adds suitable read requests to allow a read-only cache to explicitly ask for clean data. --- configs/common/CacheConfig.py | 2 +- configs/common/Caches.py | 11 +++++++++++ configs/common/O3_ARM_v7a.py | 2 ++ src/mem/cache/BaseCache.py | 1 + src/mem/cache/base.cc | 1 + src/mem/cache/base.hh | 10 ++++++++++ src/mem/cache/cache_impl.hh | 21 ++++++++++++++++++++- tests/configs/base_config.py | 12 ++++++------ tests/configs/x86_generic.py | 8 ++++---- 9 files changed, 56 insertions(+), 12 deletions(-) diff --git a/configs/common/CacheConfig.py b/configs/common/CacheConfig.py index 899090af5..d54df7490 100644 --- a/configs/common/CacheConfig.py +++ b/configs/common/CacheConfig.py @@ -64,7 +64,7 @@ def config_cache(options, system): O3_ARM_v7a_DCache, O3_ARM_v7a_ICache, O3_ARM_v7aL2 else: dcache_class, icache_class, l2_cache_class = \ - L1Cache, L1Cache, L2Cache + L1_DCache, L1_ICache, L2Cache # Set the cache line size of the system system.cache_line_size = options.cacheline_size diff --git a/configs/common/Caches.py b/configs/common/Caches.py index 6687a967c..2bdffc6c7 100644 --- a/configs/common/Caches.py +++ b/configs/common/Caches.py @@ -54,6 +54,12 @@ class L1Cache(BaseCache): tgts_per_mshr = 20 is_top_level = True +class L1_ICache(L1Cache): + is_read_only = True + +class L1_DCache(L1Cache): + pass + class L2Cache(BaseCache): assoc = 8 hit_latency = 20 @@ -81,3 +87,8 @@ class PageTableWalkerCache(BaseCache): tgts_per_mshr = 12 forward_snoops = False is_top_level = True + # the x86 table walker actually writes to the table-walker cache + if buildEnv['TARGET_ISA'] == 'x86': + is_read_only = False + else: + is_read_only = True diff --git a/configs/common/O3_ARM_v7a.py b/configs/common/O3_ARM_v7a.py index c291525ea..b4b66df9c 100644 --- a/configs/common/O3_ARM_v7a.py +++ b/configs/common/O3_ARM_v7a.py @@ -151,6 +151,7 @@ class O3_ARM_v7a_ICache(BaseCache): assoc = 2 is_top_level = True forward_snoops = False + is_read_only = True # Data Cache class O3_ARM_v7a_DCache(BaseCache): @@ -175,6 +176,7 @@ class O3_ARM_v7aWalkCache(BaseCache): write_buffers = 16 is_top_level = True forward_snoops = False + is_read_only = True # L2 Cache class O3_ARM_v7aL2(BaseCache): diff --git a/src/mem/cache/BaseCache.py b/src/mem/cache/BaseCache.py index fdb41bf75..4d6766456 100644 --- a/src/mem/cache/BaseCache.py +++ b/src/mem/cache/BaseCache.py @@ -65,6 +65,7 @@ class BaseCache(MemObject): forward_snoops = Param.Bool(True, "Forward snoops from mem side to cpu side") is_top_level = Param.Bool(False, "Is this cache at the top level (e.g. L1)") + is_read_only = Param.Bool(False, "Is this cache read only (e.g. inst)") prefetcher = Param.BasePrefetcher(NULL,"Prefetcher attached to cache") prefetch_on_access = Param.Bool(False, diff --git a/src/mem/cache/base.cc b/src/mem/cache/base.cc index c2068496c..af504d9bc 100644 --- a/src/mem/cache/base.cc +++ b/src/mem/cache/base.cc @@ -79,6 +79,7 @@ BaseCache::BaseCache(const Params *p) numTarget(p->tgts_per_mshr), forwardSnoops(p->forward_snoops), isTopLevel(p->is_top_level), + isReadOnly(p->is_read_only), blocked(0), order(0), noTargetMSHR(NULL), diff --git a/src/mem/cache/base.hh b/src/mem/cache/base.hh index aaf0ea691..d2cb11f33 100644 --- a/src/mem/cache/base.hh +++ b/src/mem/cache/base.hh @@ -309,6 +309,14 @@ class BaseCache : public MemObject * side */ const bool isTopLevel; + /** + * Is this cache read only, for example the instruction cache, or + * table-walker cache. A cache that is read only should never see + * any writes, and should never get any dirty data (and hence + * never have to do any writebacks). + */ + const bool isReadOnly; + /** * Bit vector of the blocking reasons for the access path. * @sa #BlockedCause @@ -516,6 +524,8 @@ class BaseCache : public MemObject MSHR *allocateWriteBuffer(PacketPtr pkt, Tick time, bool requestBus) { + // should only see clean evictions in a read-only cache + assert(!isReadOnly || pkt->cmd == MemCmd::CleanEvict); assert(pkt->isWrite() && !pkt->isRead()); return allocateBufferInternal(&writeBuffer, blockAlign(pkt->getAddr()), blkSize, diff --git a/src/mem/cache/cache_impl.hh b/src/mem/cache/cache_impl.hh index 117596d9b..5a9205894 100644 --- a/src/mem/cache/cache_impl.hh +++ b/src/mem/cache/cache_impl.hh @@ -299,8 +299,13 @@ Cache::access(PacketPtr pkt, CacheBlk *&blk, Cycles &lat, // sanity check assert(pkt->isRequest()); + chatty_assert(!(isReadOnly && pkt->isWrite()), + "Should never see a write in a read-only cache %s\n", + name()); + DPRINTF(Cache, "%s for %s addr %#llx size %d\n", __func__, pkt->cmdString(), pkt->getAddr(), pkt->getSize()); + if (pkt->req->isUncacheable()) { DPRINTF(Cache, "%s%s addr %#llx uncacheable\n", pkt->cmdString(), pkt->req->isInstFetch() ? " (ifetch)" : "", @@ -1419,6 +1424,7 @@ Cache::recvTimingResp(PacketPtr pkt) PacketPtr Cache::writebackBlk(CacheBlk *blk) { + chatty_assert(!isReadOnly, "Writeback from read-only cache"); assert(blk && blk->isValid() && blk->isDirty()); writebacks[Request::wbMasterId]++; @@ -1627,7 +1633,12 @@ Cache::handleFill(PacketPtr pkt, CacheBlk *blk, PacketList &writebacks) blk->status |= BlkValid | BlkReadable; if (!pkt->sharedAsserted()) { + // we could get non-shared responses from memory (rather than + // a cache) even in a read-only cache, note that we set this + // bit even for a read-only cache as we use it to represent + // the exclusive state blk->status |= BlkWritable; + // If we got this via cache-to-cache transfer (i.e., from a // cache that was an owner) and took away that owner's copy, // then we need to write it back. Normally this happens @@ -1635,8 +1646,12 @@ Cache::handleFill(PacketPtr pkt, CacheBlk *blk, PacketList &writebacks) // there are cases (such as failed store conditionals or // compare-and-swaps) where we'll demand an exclusive copy but // end up not writing it. - if (pkt->memInhibitAsserted()) + if (pkt->memInhibitAsserted()) { blk->status |= BlkDirty; + + chatty_assert(!isReadOnly, "Should never see dirty snoop response " + "in read-only cache %s\n", name()); + } } DPRINTF(Cache, "Block addr %#llx (%s) moving from state %x to %s\n", @@ -1785,6 +1800,10 @@ Cache::handleSnoop(PacketPtr pkt, CacheBlk *blk, bool is_timing, pkt->getAddr(), pkt->getSize(), blk->print()); } + chatty_assert(!(isReadOnly && blk->isDirty()), + "Should never have a dirty block in a read-only cache %s\n", + name()); + // we may end up modifying both the block state and the packet (if // we respond in atomic mode), so just figure out what to do now // and then do it later. If we find dirty data while snooping for a diff --git a/tests/configs/base_config.py b/tests/configs/base_config.py index c440d48d9..a3e1e0271 100644 --- a/tests/configs/base_config.py +++ b/tests/configs/base_config.py @@ -92,8 +92,8 @@ class BaseSystem(object): Arguments: cpu -- CPU instance to work on. """ - cpu.addPrivateSplitL1Caches(L1Cache(size='32kB', assoc=1), - L1Cache(size='32kB', assoc=4)) + cpu.addPrivateSplitL1Caches(L1_ICache(size='32kB', assoc=1), + L1_DCache(size='32kB', assoc=4)) def create_caches_shared(self, system): """Add shared caches to a system. @@ -212,8 +212,8 @@ class BaseSESystemUniprocessor(BaseSESystem): # The atomic SE configurations do not use caches if self.mem_mode == "timing": # @todo We might want to revisit these rather enthusiastic L1 sizes - cpu.addTwoLevelCacheHierarchy(L1Cache(size='128kB'), - L1Cache(size='256kB'), + cpu.addTwoLevelCacheHierarchy(L1_ICache(size='128kB'), + L1_DCache(size='256kB'), L2Cache(size='2MB')) def create_caches_shared(self, system): @@ -256,8 +256,8 @@ class BaseFSSystemUniprocessor(BaseFSSystem): BaseFSSystem.__init__(self, **kwargs) def create_caches_private(self, cpu): - cpu.addTwoLevelCacheHierarchy(L1Cache(size='32kB', assoc=1), - L1Cache(size='32kB', assoc=4), + cpu.addTwoLevelCacheHierarchy(L1_ICache(size='32kB', assoc=1), + L1_DCache(size='32kB', assoc=4), L2Cache(size='4MB', assoc=8)) def create_caches_shared(self, system): diff --git a/tests/configs/x86_generic.py b/tests/configs/x86_generic.py index 5dc8702ba..ad3ea31bf 100644 --- a/tests/configs/x86_generic.py +++ b/tests/configs/x86_generic.py @@ -81,8 +81,8 @@ class LinuxX86FSSystem(LinuxX86SystemBuilder, LinuxX86SystemBuilder.__init__(self) def create_caches_private(self, cpu): - cpu.addPrivateSplitL1Caches(L1Cache(size='32kB', assoc=1), - L1Cache(size='32kB', assoc=4), + cpu.addPrivateSplitL1Caches(L1_ICache(size='32kB', assoc=1), + L1_DCache(size='32kB', assoc=4), PageTableWalkerCache(), PageTableWalkerCache()) @@ -100,8 +100,8 @@ class LinuxX86FSSystemUniprocessor(LinuxX86SystemBuilder, LinuxX86SystemBuilder.__init__(self) def create_caches_private(self, cpu): - cpu.addTwoLevelCacheHierarchy(L1Cache(size='32kB', assoc=1), - L1Cache(size='32kB', assoc=4), + cpu.addTwoLevelCacheHierarchy(L1_ICache(size='32kB', assoc=1), + L1_DCache(size='32kB', assoc=4), L2Cache(size='4MB', assoc=8), PageTableWalkerCache(), PageTableWalkerCache())