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.
This commit is contained in:
parent
a262908acc
commit
893533a126
9 changed files with 56 additions and 12 deletions
|
@ -64,7 +64,7 @@ def config_cache(options, system):
|
||||||
O3_ARM_v7a_DCache, O3_ARM_v7a_ICache, O3_ARM_v7aL2
|
O3_ARM_v7a_DCache, O3_ARM_v7a_ICache, O3_ARM_v7aL2
|
||||||
else:
|
else:
|
||||||
dcache_class, icache_class, l2_cache_class = \
|
dcache_class, icache_class, l2_cache_class = \
|
||||||
L1Cache, L1Cache, L2Cache
|
L1_DCache, L1_ICache, L2Cache
|
||||||
|
|
||||||
# Set the cache line size of the system
|
# Set the cache line size of the system
|
||||||
system.cache_line_size = options.cacheline_size
|
system.cache_line_size = options.cacheline_size
|
||||||
|
|
|
@ -54,6 +54,12 @@ class L1Cache(BaseCache):
|
||||||
tgts_per_mshr = 20
|
tgts_per_mshr = 20
|
||||||
is_top_level = True
|
is_top_level = True
|
||||||
|
|
||||||
|
class L1_ICache(L1Cache):
|
||||||
|
is_read_only = True
|
||||||
|
|
||||||
|
class L1_DCache(L1Cache):
|
||||||
|
pass
|
||||||
|
|
||||||
class L2Cache(BaseCache):
|
class L2Cache(BaseCache):
|
||||||
assoc = 8
|
assoc = 8
|
||||||
hit_latency = 20
|
hit_latency = 20
|
||||||
|
@ -81,3 +87,8 @@ class PageTableWalkerCache(BaseCache):
|
||||||
tgts_per_mshr = 12
|
tgts_per_mshr = 12
|
||||||
forward_snoops = False
|
forward_snoops = False
|
||||||
is_top_level = True
|
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
|
||||||
|
|
|
@ -151,6 +151,7 @@ class O3_ARM_v7a_ICache(BaseCache):
|
||||||
assoc = 2
|
assoc = 2
|
||||||
is_top_level = True
|
is_top_level = True
|
||||||
forward_snoops = False
|
forward_snoops = False
|
||||||
|
is_read_only = True
|
||||||
|
|
||||||
# Data Cache
|
# Data Cache
|
||||||
class O3_ARM_v7a_DCache(BaseCache):
|
class O3_ARM_v7a_DCache(BaseCache):
|
||||||
|
@ -175,6 +176,7 @@ class O3_ARM_v7aWalkCache(BaseCache):
|
||||||
write_buffers = 16
|
write_buffers = 16
|
||||||
is_top_level = True
|
is_top_level = True
|
||||||
forward_snoops = False
|
forward_snoops = False
|
||||||
|
is_read_only = True
|
||||||
|
|
||||||
# L2 Cache
|
# L2 Cache
|
||||||
class O3_ARM_v7aL2(BaseCache):
|
class O3_ARM_v7aL2(BaseCache):
|
||||||
|
|
1
src/mem/cache/BaseCache.py
vendored
1
src/mem/cache/BaseCache.py
vendored
|
@ -65,6 +65,7 @@ class BaseCache(MemObject):
|
||||||
forward_snoops = Param.Bool(True,
|
forward_snoops = Param.Bool(True,
|
||||||
"Forward snoops from mem side to cpu side")
|
"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_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")
|
prefetcher = Param.BasePrefetcher(NULL,"Prefetcher attached to cache")
|
||||||
prefetch_on_access = Param.Bool(False,
|
prefetch_on_access = Param.Bool(False,
|
||||||
|
|
1
src/mem/cache/base.cc
vendored
1
src/mem/cache/base.cc
vendored
|
@ -79,6 +79,7 @@ BaseCache::BaseCache(const Params *p)
|
||||||
numTarget(p->tgts_per_mshr),
|
numTarget(p->tgts_per_mshr),
|
||||||
forwardSnoops(p->forward_snoops),
|
forwardSnoops(p->forward_snoops),
|
||||||
isTopLevel(p->is_top_level),
|
isTopLevel(p->is_top_level),
|
||||||
|
isReadOnly(p->is_read_only),
|
||||||
blocked(0),
|
blocked(0),
|
||||||
order(0),
|
order(0),
|
||||||
noTargetMSHR(NULL),
|
noTargetMSHR(NULL),
|
||||||
|
|
10
src/mem/cache/base.hh
vendored
10
src/mem/cache/base.hh
vendored
|
@ -309,6 +309,14 @@ class BaseCache : public MemObject
|
||||||
* side */
|
* side */
|
||||||
const bool isTopLevel;
|
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.
|
* Bit vector of the blocking reasons for the access path.
|
||||||
* @sa #BlockedCause
|
* @sa #BlockedCause
|
||||||
|
@ -516,6 +524,8 @@ class BaseCache : public MemObject
|
||||||
|
|
||||||
MSHR *allocateWriteBuffer(PacketPtr pkt, Tick time, bool requestBus)
|
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());
|
assert(pkt->isWrite() && !pkt->isRead());
|
||||||
return allocateBufferInternal(&writeBuffer,
|
return allocateBufferInternal(&writeBuffer,
|
||||||
blockAlign(pkt->getAddr()), blkSize,
|
blockAlign(pkt->getAddr()), blkSize,
|
||||||
|
|
21
src/mem/cache/cache_impl.hh
vendored
21
src/mem/cache/cache_impl.hh
vendored
|
@ -299,8 +299,13 @@ Cache::access(PacketPtr pkt, CacheBlk *&blk, Cycles &lat,
|
||||||
// sanity check
|
// sanity check
|
||||||
assert(pkt->isRequest());
|
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__,
|
DPRINTF(Cache, "%s for %s addr %#llx size %d\n", __func__,
|
||||||
pkt->cmdString(), pkt->getAddr(), pkt->getSize());
|
pkt->cmdString(), pkt->getAddr(), pkt->getSize());
|
||||||
|
|
||||||
if (pkt->req->isUncacheable()) {
|
if (pkt->req->isUncacheable()) {
|
||||||
DPRINTF(Cache, "%s%s addr %#llx uncacheable\n", pkt->cmdString(),
|
DPRINTF(Cache, "%s%s addr %#llx uncacheable\n", pkt->cmdString(),
|
||||||
pkt->req->isInstFetch() ? " (ifetch)" : "",
|
pkt->req->isInstFetch() ? " (ifetch)" : "",
|
||||||
|
@ -1419,6 +1424,7 @@ Cache::recvTimingResp(PacketPtr pkt)
|
||||||
PacketPtr
|
PacketPtr
|
||||||
Cache::writebackBlk(CacheBlk *blk)
|
Cache::writebackBlk(CacheBlk *blk)
|
||||||
{
|
{
|
||||||
|
chatty_assert(!isReadOnly, "Writeback from read-only cache");
|
||||||
assert(blk && blk->isValid() && blk->isDirty());
|
assert(blk && blk->isValid() && blk->isDirty());
|
||||||
|
|
||||||
writebacks[Request::wbMasterId]++;
|
writebacks[Request::wbMasterId]++;
|
||||||
|
@ -1627,7 +1633,12 @@ Cache::handleFill(PacketPtr pkt, CacheBlk *blk, PacketList &writebacks)
|
||||||
blk->status |= BlkValid | BlkReadable;
|
blk->status |= BlkValid | BlkReadable;
|
||||||
|
|
||||||
if (!pkt->sharedAsserted()) {
|
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;
|
blk->status |= BlkWritable;
|
||||||
|
|
||||||
// If we got this via cache-to-cache transfer (i.e., from a
|
// 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,
|
// cache that was an owner) and took away that owner's copy,
|
||||||
// then we need to write it back. Normally this happens
|
// 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
|
// there are cases (such as failed store conditionals or
|
||||||
// compare-and-swaps) where we'll demand an exclusive copy but
|
// compare-and-swaps) where we'll demand an exclusive copy but
|
||||||
// end up not writing it.
|
// end up not writing it.
|
||||||
if (pkt->memInhibitAsserted())
|
if (pkt->memInhibitAsserted()) {
|
||||||
blk->status |= BlkDirty;
|
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",
|
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());
|
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 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
|
// 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
|
// and then do it later. If we find dirty data while snooping for a
|
||||||
|
|
|
@ -92,8 +92,8 @@ class BaseSystem(object):
|
||||||
Arguments:
|
Arguments:
|
||||||
cpu -- CPU instance to work on.
|
cpu -- CPU instance to work on.
|
||||||
"""
|
"""
|
||||||
cpu.addPrivateSplitL1Caches(L1Cache(size='32kB', assoc=1),
|
cpu.addPrivateSplitL1Caches(L1_ICache(size='32kB', assoc=1),
|
||||||
L1Cache(size='32kB', assoc=4))
|
L1_DCache(size='32kB', assoc=4))
|
||||||
|
|
||||||
def create_caches_shared(self, system):
|
def create_caches_shared(self, system):
|
||||||
"""Add shared caches to a system.
|
"""Add shared caches to a system.
|
||||||
|
@ -212,8 +212,8 @@ class BaseSESystemUniprocessor(BaseSESystem):
|
||||||
# The atomic SE configurations do not use caches
|
# The atomic SE configurations do not use caches
|
||||||
if self.mem_mode == "timing":
|
if self.mem_mode == "timing":
|
||||||
# @todo We might want to revisit these rather enthusiastic L1 sizes
|
# @todo We might want to revisit these rather enthusiastic L1 sizes
|
||||||
cpu.addTwoLevelCacheHierarchy(L1Cache(size='128kB'),
|
cpu.addTwoLevelCacheHierarchy(L1_ICache(size='128kB'),
|
||||||
L1Cache(size='256kB'),
|
L1_DCache(size='256kB'),
|
||||||
L2Cache(size='2MB'))
|
L2Cache(size='2MB'))
|
||||||
|
|
||||||
def create_caches_shared(self, system):
|
def create_caches_shared(self, system):
|
||||||
|
@ -256,8 +256,8 @@ class BaseFSSystemUniprocessor(BaseFSSystem):
|
||||||
BaseFSSystem.__init__(self, **kwargs)
|
BaseFSSystem.__init__(self, **kwargs)
|
||||||
|
|
||||||
def create_caches_private(self, cpu):
|
def create_caches_private(self, cpu):
|
||||||
cpu.addTwoLevelCacheHierarchy(L1Cache(size='32kB', assoc=1),
|
cpu.addTwoLevelCacheHierarchy(L1_ICache(size='32kB', assoc=1),
|
||||||
L1Cache(size='32kB', assoc=4),
|
L1_DCache(size='32kB', assoc=4),
|
||||||
L2Cache(size='4MB', assoc=8))
|
L2Cache(size='4MB', assoc=8))
|
||||||
|
|
||||||
def create_caches_shared(self, system):
|
def create_caches_shared(self, system):
|
||||||
|
|
|
@ -81,8 +81,8 @@ class LinuxX86FSSystem(LinuxX86SystemBuilder,
|
||||||
LinuxX86SystemBuilder.__init__(self)
|
LinuxX86SystemBuilder.__init__(self)
|
||||||
|
|
||||||
def create_caches_private(self, cpu):
|
def create_caches_private(self, cpu):
|
||||||
cpu.addPrivateSplitL1Caches(L1Cache(size='32kB', assoc=1),
|
cpu.addPrivateSplitL1Caches(L1_ICache(size='32kB', assoc=1),
|
||||||
L1Cache(size='32kB', assoc=4),
|
L1_DCache(size='32kB', assoc=4),
|
||||||
PageTableWalkerCache(),
|
PageTableWalkerCache(),
|
||||||
PageTableWalkerCache())
|
PageTableWalkerCache())
|
||||||
|
|
||||||
|
@ -100,8 +100,8 @@ class LinuxX86FSSystemUniprocessor(LinuxX86SystemBuilder,
|
||||||
LinuxX86SystemBuilder.__init__(self)
|
LinuxX86SystemBuilder.__init__(self)
|
||||||
|
|
||||||
def create_caches_private(self, cpu):
|
def create_caches_private(self, cpu):
|
||||||
cpu.addTwoLevelCacheHierarchy(L1Cache(size='32kB', assoc=1),
|
cpu.addTwoLevelCacheHierarchy(L1_ICache(size='32kB', assoc=1),
|
||||||
L1Cache(size='32kB', assoc=4),
|
L1_DCache(size='32kB', assoc=4),
|
||||||
L2Cache(size='4MB', assoc=8),
|
L2Cache(size='4MB', assoc=8),
|
||||||
PageTableWalkerCache(),
|
PageTableWalkerCache(),
|
||||||
PageTableWalkerCache())
|
PageTableWalkerCache())
|
||||||
|
|
Loading…
Reference in a new issue