直接映射Cache模拟器
直接映射是Cache中结构最简单的一种。
一、Cache的大小和结构
直接映射Cache的结构如下:(因为每组只有一行,所以下文中行、组代表相同含义)
代码如下:
本实验采用总容量16KB,每一组(行)64字节,则共256组(行)
typedef unsigned char UINT8;
typedef unsigned short UINT16;
typedef unsigned int UINT32;
typedef int INT32;
typedef unsigned long long UINT64; // 数字表示数据的位数
#define DCACHE_SIZE 16384 // 总大小16KB
#define DCACHE_DATA_PER_LINE 64 // 每行64字节
#define DCACHE_DATA_PER_LINE_ADDR_BITS GET_POWER_OF_2(DCACHE_DATA_PER_LINE) // 2^6=64,即64字节需要6位地址
#define DCACHE_SET (DCACHE_SIZE / DCACHE_DATA_PER_LINE) // 256行
#define DCACHE_SET_ADDR_BITS GET_POWER_OF_2(DCACHE_SET) // 2^8=256,即256行需要8位地址
定义一个DCache数组,每个元素表示一行(组)的结构:
struct DCACHE_LineStruct
{
UINT8 Valid; // 只要从主存加载过数据到该行,Valid变为1
UINT8 Dirty; // 标识该行是否被修改过(写回策略)
UINT64 Tag; // 行标记,用于区分映射到该行的不同地址的数据
UINT8 Data[DCACHE_DATA_PER_LINE]; // 数据块,每个1字节,共64字节
} DCache[DCACHE_SET];
DCache的初始化:
void InitDataCache()
{
UINT32 i;
for (i = 0; i < DCACHE_SET; i++)
{
DCache[i].Valid = 0; // 最初从未从主存加载过数据,Valid=0
DCache[i].Dirty = 0; // 最初数据未被修改过,Dirty=0
}
}
二、Cache对读、写操作的处理
参数:Address主存地址;
operation操作类型,'R’读’W’写;
DataSize数据的大小(字节数);
StoreValue: 当执行写操作的时候,需要写入的数据;
LoadResult: 当执行读操作的时候,从Cache读出的数据
首先要对地址进行切分,得到标记、行号、块偏移
CacheLineAddress = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) % DCACHE_SET; // Cache的行号,在直接映射中,就是组号(每组1行)
BlockOffset = Address % DCACHE_DATA_PER_LINE; // 块偏移,数据起始字节所在的位置
AddressTag = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) >> DCACHE_SET_ADDR_BITS; // 地址去掉DCACHE_SET、DCACHE_DATA_PER_LINE,剩下的作为Tag
根据行号索引到对应组,再根据有效位和标记判断是否命中
(一)命中时
即满足两个条件:索引到的行有效,且标记位与AddressTag相同
1. 读操作
根据需要的字节数直接读取即可
注意要对齐到相应字节边界,如4字节:BlockOffset & 0xFC,即把最后2位设置为0,一定为4的倍数
ReadValue = 0;
switch (DataSize)
{
case 1: // 1个字节
ReadValue = DCache[CacheLineAddress].Data[BlockOffset + 0];
break;
case 2: // 2个字节
BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界
ReadValue = DCache[CacheLineAddress].Data[BlockOffset + 1];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 0];
break;
case 4: // 4个字节
BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界
ReadValue = DCache[CacheLineAddress].Data[BlockOffset + 3];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 2];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 1];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 0];
break;
case 8: // 8个字节
BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界
ReadValue = DCache[CacheLineAddress].Data[BlockOffset + 7];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 6];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 5];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 4];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 3];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 2];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 1];
ReadValue = ReadValue << 8;
ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 0];
break;
}
*LoadResult = ReadValue;
2.写操作
由于修改了Cache的值,涉及到要不要修改相应主存地址的值,有两种策略:写直达和写回。
一般采用效率更高的写回
即先不修改主存内的数据,一直用Cache中的那个,直到其将要被驱逐时,再修改主存的值。
实现起来需要引入Dirty标记,逻辑为:新加载到Cache一行时,其Dirty设置为0,表示未被修改。当用写操作修改了Cache中这行的数据后,其Dirty设置为1,表示其已经和主存的值不同了。如果要驱逐这行,驱逐前要把这行修改过的块存到它原来所在的地址。
switch (DataSize)
{
case 1: // 1个字节
DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF;
break;
case 2: // 2个字节
BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界
DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 1] = StoreValue & 0xFF;
break;
case 4: // 4个字节
BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界
DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 1] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 2] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 3] = StoreValue & 0xFF;
break;
case 8: // 8个字节
BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界
DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 1] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 2] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 3] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 4] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 5] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 6] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 7] = StoreValue & 0xFF;
break;
}
DCache[CacheLineAddress].Dirty = 1;//由于写入后数据被修改,Dirty设为1
(二)未命中
需要从主存中加载数据到Cache中。
在此之前,根据写回策略,如果这一行已经被修改(Dirty=1),需要先倒推出其在主存的地址,并存回去。
if (DCache[CacheLineAddress].Dirty)
{
UINT64 OldAddress; // 计算原地址,OldAddress = > (Tag,Set,0000)
OldAddress = ((DCache[CacheLineAddress].Tag << DCACHE_SET_ADDR_BITS) << DCACHE_DATA_PER_LINE_ADDR_BITS) | ((UINT64)CacheLineAddress << DCACHE_DATA_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址
StoreDataCacheLineToMemory(OldAddress, CacheLineAddress);
DCache[CacheLineAddress].Dirty = 0; // 将新加载一行,Dirty设为1
}
再加载数据,设置有效位和标记
LoadDataCacheLineFromMemory(Address, CacheLineAddress);
DCache[CacheLineAddress].Valid = 1;
DCache[CacheLineAddress].Tag = AddressTag;
1.读操作
不用读了
2.写操作
写回策略一般搭配写分配,即加载到Cache后只修改Cache中的数据,主存不变
和命中时的写一样
switch (DataSize)
{
case 1: // 1个字节
DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF;
break;
case 2: // 2个字节
BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界
DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 1] = StoreValue & 0xFF;
break;
case 4: // 4个字节
BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界
DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 1] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 2] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 3] = StoreValue & 0xFF;
break;
case 8: // 8个字节
BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界
DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 1] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 2] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 3] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 4] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 5] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 6] = StoreValue & 0xFF;
StoreValue = StoreValue >> 8;
DCache[CacheLineAddress].Data[BlockOffset + 7] = StoreValue & 0xFF;
break;
}
(三)Load和Store函数
1.Load函数
从内存加载一块数据到Cache中
void LoadDataCacheLineFromMemory(UINT64 Address, UINT32 CacheLineAddress)
{
// 一次性从Memory中将DCACHE_DATA_PER_LINE数据读入某个Data Cache行
// 提供了一个函数,一次可以读入8个字节
UINT32 i;
UINT64 ReadData;
UINT64 AlignAddress;
UINT64 *pp;
AlignAddress = Address & ~(DCACHE_DATA_PER_LINE - 1); // 地址必须对齐到DCACHE_DATA_PER_LINE (64)字节边界
pp = (UINT64 *)DCache[CacheLineAddress].Data;
for (i = 0; i < DCACHE_DATA_PER_LINE / 8; i++)
{
ReadData = ReadMemory(AlignAddress + 8LL * i);
if (DEBUG)
printf("[%s] Address=%016llX ReadData=%016llX\n", __func__, AlignAddress + 8LL * i, ReadData);
pp[i] = ReadData;
}
}
2.Store函数
从Cache中把一行存回主存
void StoreDataCacheLineToMemory(UINT64 Address, UINT32 CacheLineAddress)
{
// 一次性将DCACHE_DATA_PER_LINE数据从某个Data Cache行写入Memory中
// 提供了一个函数,一次可以写入8个字节
UINT32 i;
UINT64 WriteData;
UINT64 AlignAddress;
UINT64 *pp;
AlignAddress = Address & ~(DCACHE_DATA_PER_LINE - 1); // 地址必须对齐到DCACHE_DATA_PER_LINE (64)字节边界
pp = (UINT64 *)DCache[CacheLineAddress].Data;
WriteData = 0;
for (i = 0; i < DCACHE_DATA_PER_LINE / 8; i++)
{
WriteData = pp[i];
WriteMemory(AlignAddress + 8LL * i, WriteData);
if (DEBUG)
printf("[%s] Address=%016llX ReadData=%016llX\n", __func__, AlignAddress + 8LL * i, WriteData);
}
}