/*******************************************************************************
* openDLX - A DLX/MIPS processor simulator.
* Copyright (C) 2013 The openDLX project, University of Augsburg, Germany
* Project URL: <https://sourceforge.net/projects/opendlx>
* Development branch: <https://github.com/smetzlaff/openDLX>
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program, see <LICENSE>. If not, see
* <http://www.gnu.org/licenses/>.
******************************************************************************/
package openDLX.memory;
import org.apache.log4j.Logger;
import openDLX.PipelineConstants;
import openDLX.datatypes.CacheReplacementPolicy;
import openDLX.datatypes.CacheType;
import openDLX.datatypes.DCacheWritePolicy;
import openDLX.datatypes.RequestType;
import openDLX.datatypes.uint32;
import openDLX.datatypes.uint8;
import openDLX.exception.CacheException;
import openDLX.exception.MemoryException;
import openDLX.exception.PipelineDataTypeException;
import openDLX.util.CacheAddressCalculator;
import openDLX.util.CalculationHelper;
import openDLX.util.Statistics;
public abstract class Cache implements MemoryInterface {
private Logger logger;
protected Statistics stat = Statistics.getInstance();
protected CacheType cache_type;
protected MainMemory mem;
/// Size of the cache line in bytes
protected int line_size;
protected int words_per_line;
protected int line_no;
protected int lines_per_set;
protected int associativity;
protected CacheReplacementPolicy policy;
private DCacheWritePolicy write_policy;
protected int block_offset_size;
protected int tag_size;
protected int index_size;
protected CacheLine cache_memory[][];
public Cache(CacheType type, int line_size, int line_no, int associativity, DCacheWritePolicy write_policy, MainMemory mem) throws CacheException, PipelineDataTypeException
{
if(type != CacheType.DCACHE)
{
throw new CacheException("Cache type: " + type + " is not by this constructor.");
}
this.cache_type = type;
this.line_size = line_size;
this.line_no = line_no;
this.associativity = associativity;
this.mem = mem;
this.write_policy = write_policy;
initialize();
}
public Cache(CacheType type, int line_size, int line_no, int associativity, MainMemory mem) throws CacheException, PipelineDataTypeException
{
if(type != CacheType.ICACHE)
{
throw new CacheException("Cache type: " + cache_type + " is by in this constructor.");
}
this.cache_type = type;
this.line_size = line_size;
this.line_no = line_no;
this.associativity = associativity;
this.mem = mem;
// ICACHE has no write policy
this.write_policy = DCacheWritePolicy.UNKNOWN;
initialize();
}
private void initialize() throws CacheException, PipelineDataTypeException
{
if(cache_type == CacheType.ICACHE)
{
logger = Logger.getLogger("ICache");
}
else if(cache_type == CacheType.DCACHE)
{
logger = Logger.getLogger("DCache");
}
else
{
logger = Logger.getLogger("UNKNOWNCache");
}
// enforce that the line number, line size and the associativity is a power of two
if(Integer.bitCount(line_size) != 1)
{
throw new CacheException("The size of the cache lines has to be a power of two, but it is: " + line_size);
}
if(Integer.bitCount(line_no) != 1)
{
throw new CacheException("The number of cache lines has to be a power of two, but it is: " + line_no);
}
if((Integer.bitCount(associativity) != 1) && (associativity != 0))
{
throw new CacheException("The cache associativity has to be a power of two, but it is: " + associativity);
}
if(associativity == 0)
{
// fully associative cache:
associativity = line_no;
}
if(line_no < associativity)
{
throw new CacheException("The cache associativity cannot be higher than the number of cache lines. Associativity: " + associativity + " Cache lines: " + line_no);
}
lines_per_set = line_no / associativity;
cache_memory = new CacheLine[this.associativity][this.lines_per_set];
CacheAddressCalculator cac = new CacheAddressCalculator(this.line_no, this.line_size, this.associativity, PipelineConstants.ADDR_WIDTH);
tag_size = cac.getTagSize();
index_size = cac.getIndexSize();
block_offset_size = cac.getBlockOffsetSize();
if(line_size % PipelineConstants.WORD_SIZE != 0)
{
throw new CacheException("The cache line size has to be a multiple of the word size (" + PipelineConstants.WORD_SIZE + "), but it is set to " + line_size);
}
words_per_line = cac.getWordsPerLine();
for(int j = 0; j < this.associativity; j++)
{
for(int i = 0; i < this.lines_per_set; i++)
{
cache_memory[j][i] = new CacheLine(this.line_size, this.tag_size);
}
}
logger.info("Initialized " + cache_type + " with " + associativity + " ways, " + line_no + " lines, " + lines_per_set + " lines per set, " + words_per_line + " words per line, " + tag_size + " bits for tag, " + index_size + " bits for index, and " + block_offset_size + " bits for block offset. Cache is of type: " + this.getClass());
}
public uint32 read_u32(uint32 addr) throws MemoryException
{
return read_u32(addr, false);
}
public uint32 read_u32(uint32 addr, boolean log_output) throws MemoryException
{
uint32 value = new uint32();
if(log_output)
{
logger.debug("Read u32 from addr: " + addr.getValueAsHexString());
}
if(isHit(addr))
{
int index = getIndex(addr);
int way = getCacheWayForHit(addr);
value.setValue(cache_memory[way][index].getWord(getBlockOffset(addr)));
if(log_output)
{
logger.debug("Hit in way " + way + " in cache line " + index + " for address " + addr.getValueAsHexString() + " value: " + value.getValueAsHexString());
dumpCacheLine(index);
}
updateReplacementCountersOnAccess(way, index);
stat.countCacheHit(cache_type);
}
else
{
int cache_line_address = getCacheLineAddr(addr);
byte line[] = new byte[line_size];
for(int i = 0; i < line_size; i++)
{
line[i] = mem.read_u8(new uint32(cache_line_address + i)).getValue();
}
value.setValue(mem.read_u32(addr));
int index = getIndex(addr);
int way = getCacheWayForReplacement(addr);
logger.debug("Accessing way: " + way + " index: " + index);
cache_memory[way][index].setLine(getTagFromAddress(addr), line);
if(log_output)
{
logger.debug("Miss in cache for address " + addr.getValueAsHexString() + " replaced cache line " + index + " in way " + way + " loaded value: " + value.getValueAsHexString());
dumpCacheLine(index);
}
updateReplacementCountersOnMiss(way, index);
stat.countCacheMiss(cache_type);
}
return value;
}
public uint8 read_u8(uint32 addr, boolean log_output) throws MemoryException
{
if(cache_type != CacheType.DCACHE)
{
throw new CacheException("Method read_u8() only supports data caches, but cache type is: " + cache_type);
}
uint8 value = new uint8();
if(log_output)
{
logger.debug("Read u8 from addr: " + addr.getValueAsHexString());
}
if(isHit(addr))
{
int index = getIndex(addr);
int way = getCacheWayForHit(addr);
value.setValue(cache_memory[way][index].getByte(getBlockOffset(addr)));
if(log_output)
{
logger.debug("Hit in way " + way + " in cache line " + index + " for address " + addr.getValueAsHexString() + " value: " + value.getValueAsHexString() + " (read byte " + (getBlockOffset(addr)&0x3) + " from word " + cache_memory[way][index].getWord(getBlockOffset(addr)&(~0x3)) + ")");
dumpCacheLine(index);
}
updateReplacementCountersOnAccess(way, index);
}
else
{
int cache_line_address = getCacheLineAddr(addr);
byte line[] = new byte[line_size];
for(int i = 0; i < line_size; i++)
{
line[i] = mem.read_u8(new uint32(cache_line_address + i)).getValue();
}
value.setValue(mem.read_u8(addr));
int index = getIndex(addr);
int way = getCacheWayForReplacement(addr);
cache_memory[way][index].setLine(getTagFromAddress(addr), line);
if(log_output)
{
logger.debug("Miss in cache for address " + addr.getValueAsHexString() + " replaced cache line " + index + " in way " + way + " loaded value: " + value.getValueAsHexString() + " (read byte " + (getBlockOffset(addr)&0x3) + " from word " + cache_memory[way][index].getWord(getBlockOffset(addr)&(~0x3)) + ")");
dumpCacheLine(index);
}
updateReplacementCountersOnMiss(way, index);
}
return value;
}
public void write_u32(uint32 addr, uint32 value) throws MemoryException
{
if(cache_type != CacheType.DCACHE)
{
throw new CacheException("Method write_u32() only supports data caches, but cache type is: " + cache_type);
}
if(write_policy != DCacheWritePolicy.WRITE_THROUGH)
{
throw new CacheException("Currently only write through caches are supported, but cache write policy is: " + write_policy);
}
if((addr.getValue()&0x3 ) != 0)
{
logger.error("Write u32 to unaligned addr: " + addr.getValueAsHexString());
throw new CacheException("Write u32 to unaligned addr: " + addr.getValueAsHexString());
}
logger.debug("Write u32 to addr: " + addr.getValueAsHexString() + " value: " + value.getValueAsHexString());
if(isHit(addr))
{
int index = getIndex(addr);
int way = getCacheWayForHit(addr);
uint32 old_value = cache_memory[way][index].getWord(getBlockOffset(addr));
cache_memory[way][index].setWord(getBlockOffset(addr), value);
logger.debug("Hit in way " + way + " in cache line " + index + " for address " + addr.getValueAsHexString() + " old value: " + old_value.getValueAsHexString() + " new value: " + cache_memory[way][index].getWord(getBlockOffset(addr)));
dumpCacheLine(index);
updateReplacementCountersOnAccess(way, index);
}
else
{
int cache_line_address = getCacheLineAddr(addr);
uint32 old_value = new uint32();
byte line[] = new byte[line_size];
for(int i = 0; i < line_size; i++)
{
line[i] = mem.read_u8(new uint32(cache_line_address + i)).getValue();
}
old_value.setValue(mem.read_u32(addr));
int index = getIndex(addr);
int way = getCacheWayForReplacement(addr);
// load cache line from memory
cache_memory[way][index].setLine(getTagFromAddress(addr), line);
// write word into cache
cache_memory[way][index].setWord(getBlockOffset(addr), value);
logger.debug("Miss in cache for address " + addr.getValueAsHexString() + " replaced cache line " + index + " in way " + way + " old_value: " + old_value.getValueAsHexString() + " new value: " + cache_memory[way][index].getWord(getBlockOffset(addr)));
dumpCacheLine(index);
updateReplacementCountersOnMiss(way, index);
}
if(write_policy == DCacheWritePolicy.WRITE_THROUGH)
{
// also always write value into memory
mem.write_u32(addr, value);
}
}
public void write_u8(uint32 addr, uint32 value) throws MemoryException
{
write_u8(addr, new uint8(value.getValue()));
}
public void write_u8(uint32 addr, uint8 value) throws MemoryException
{
if(cache_type != CacheType.DCACHE)
{
throw new CacheException("Method write_u32() only supports data caches, but cache type is: " + cache_type);
}
if(write_policy != DCacheWritePolicy.WRITE_THROUGH)
{
throw new CacheException("Currently only write through caches are supported, but cache write policy is: " + write_policy);
}
logger.debug("Write u8 to addr: " + addr.getValueAsHexString() + " value: " + value.getValueAsHexString());
if(isHit(addr))
{
int index = getIndex(addr);
int way = getCacheWayForHit(addr);
uint8 old_value = cache_memory[way][index].getByte(getBlockOffset(addr));
cache_memory[way][index].setByte(getBlockOffset(addr), value);
logger.debug("Hit in way " + way + " in cache line " + index + " for address " + addr.getValueAsHexString() + " old value: " + old_value.getValueAsHexString() + " new value: " + cache_memory[way][index].getByte(getBlockOffset(addr)) + " (written byte " + (getBlockOffset(addr)&0x3) + " of word " + cache_memory[way][index].getWord(getBlockOffset(addr)&(~0x3)) + ")");
dumpCacheLine(index);
updateReplacementCountersOnAccess(way, index);
}
else
{
int cache_line_address = getCacheLineAddr(addr);
uint32 old_value = new uint32();
byte line[] = new byte[line_size];
for(int i = 0; i < line_size; i++)
{
line[i] = mem.read_u8(new uint32(cache_line_address + i)).getValue();
}
old_value.setValue(mem.read_u32(addr));
int index = getIndex(addr);
int way = getCacheWayForReplacement(addr);
// load cache line from memory
cache_memory[way][index].setLine(getTagFromAddress(addr), line);
// write word into cache
cache_memory[way][index].setByte(getBlockOffset(addr), value);
logger.debug("Miss in cache for address " + addr.getValueAsHexString() + " replaced cache line " + index + " in way " + way + " old_value: " + old_value.getValueAsHexString() + " new value: " + cache_memory[way][index].getByte(getBlockOffset(addr)) + " (written byte " + (getBlockOffset(addr)&0x3) + " of word " + cache_memory[way][index].getWord(getBlockOffset(addr)&(~0x3)) + ")");
dumpCacheLine(index);
updateReplacementCountersOnMiss(way, index);
}
if(write_policy == DCacheWritePolicy.WRITE_THROUGH)
{
// also always write value into memory
mem.write_u8(addr, value);
}
}
protected uint32 getTagFromAddress(uint32 addr)
{
int mask = ~(CalculationHelper.generateBitStringOfOnes(index_size + block_offset_size));
int tag = addr.getValue() & mask;
return new uint32(tag);
}
protected int getIndex(uint32 addr)
{
int mask = (CalculationHelper.generateBitStringOfOnes(index_size));
logger.debug("idx: " + Integer.toHexString(CalculationHelper.generateBitStringOfOnes(index_size)) + " index mask: 0x" + Integer.toHexString(mask) + " value: " + addr.getValueAsHexString() + " result: 0x" + Integer.toHexString((addr.getValue()>> block_offset_size) & mask));
int index = (addr.getValue()>> (block_offset_size)) & mask;
return index;
}
protected int getBlockOffset(uint32 addr)
{
return addr.getValue() & (CalculationHelper.generateBitStringOfOnes(block_offset_size));
}
protected int getCacheLineAddr(uint32 addr)
{
return addr.getValue() & ~(CalculationHelper.generateBitStringOfOnes(block_offset_size));
}
public short getRequestDelay(RequestType type, uint32 addr) throws MemoryException
{
short latency = 0;
switch(this.cache_type)
{
case ICACHE:
{
if(type != RequestType.INSTR_RD)
{
throw new CacheException("Unsupported request type for instruction cache: " + type);
}
if(isHit(addr))
{
latency = 0;
}
else
{
latency = mem.getRequestDelay(type, addr);
}
break;
}
case DCACHE:
{
if((type != RequestType.DATA_RD) && (type != RequestType.DATA_WR))
{
throw new CacheException("Unsupported request type for data cache: " + type);
}
if(type == RequestType.DATA_RD)
{
if(isHit(addr))
{
latency = 0;
}
else
{
latency = mem.getRequestDelay(type, addr);
}
}
else if(type == RequestType.DATA_WR)
{
switch(write_policy)
{
// TODO to be done.
// case WRITE_BACK:
// latency = 0;
// break;
case WRITE_THROUGH:
latency = mem.getRequestDelay(type, addr);
break;
default:
throw new CacheException("Currently only write through caches are supported, but cache write policy is: " + write_policy);
}
}
break;
}
default:
throw new CacheException("Unknown cache type: " + cache_type);
}
return latency;
}
public boolean isHit(uint32 addr) throws CacheException
{
uint32 tag = getTagFromAddress(addr);
int index = getIndex(addr);
boolean hit = false;
logger.debug("Accessing index: " + index);
for(int i = 0; i < associativity; i++)
{
if(cache_memory[i][index].compareTag(tag))
{
if(hit != false)
{
throw new CacheException("Error: multiple hits for " + addr.getValueAsHexString() + " in cache.");
}
hit = true;
}
}
return hit;
}
protected int getCacheWayForHit(uint32 addr) throws CacheException
{
uint32 tag = getTagFromAddress(addr);
int index = getIndex(addr);
int hit_way = associativity;
for(int i = 0; i < associativity; i++)
{
if(cache_memory[i][index].compareTag(tag))
{
if(hit_way != associativity)
{
throw new CacheException("Error: multiple hits for " + addr.getValueAsHexString() + " in cache.");
}
hit_way = i;
}
}
return hit_way;
}
protected void dumpCacheLine(int index)
{
logger.debug("Dumping cache content for index: 0x" + Integer.toHexString(index));
for(int i = 0; i < associativity; i++)
{
logger.debug("Way: " + i + " " + cache_memory[i][index].dumpLine());
}
}
protected abstract int getCacheWayForReplacement(uint32 addr) throws CacheException;
protected abstract void updateReplacementCountersOnAccess(int way, int index);
protected abstract void updateReplacementCountersOnMiss(int way, int index);
public static CacheReplacementPolicy getCacheReplacementPolicyFromString(String rpol)
{
if(rpol.compareTo(CacheReplacementPolicy.FIFO.toString())==0)
{
return CacheReplacementPolicy.FIFO;
}
else if(rpol.compareTo(CacheReplacementPolicy.LRU.toString())==0)
{
return CacheReplacementPolicy.LRU;
}
return CacheReplacementPolicy.UNKNOWN;
}
public static DCacheWritePolicy getCacheWritePolicyFromString(String wpol)
{
if(wpol.compareTo(DCacheWritePolicy.WRITE_THROUGH.toString())==0)
{
return DCacheWritePolicy.WRITE_THROUGH;
}
else if(wpol.compareTo(DCacheWritePolicy.WRITE_BACK.toString())==0)
{
return DCacheWritePolicy.WRITE_BACK;
}
return DCacheWritePolicy.UNKNOWN;
}
}