目录
6、LoadDataCacheLineFromMemory()、StoreDataCacheLineToMemory()
一、题目要求
本实验的主要任务,是实现一个组相联的Data Cache,达到最高命中率。 参考设计给出了一个直接映射的Data Cache实现,你可以首先尝试写一个全相联的Data Cache实现,然后开始实现一个组相联的Data Cache。 你只能修改Cache.c文件。 你的Data Cache最大只能使用16KB的数据存储空间。 你可以尝试写一个Inst Cache,用于处理Trace中的指令访问。
【高阶要求】有能力的同学,可以考虑在以上基础上,实现 1、4KB的牺牲Cache(Victim Cache) 2、1MB的L2统一Cache
【提示】你可以首先运行较小的trace,例如dave.trace.zst等,来验证你的实现的正确性。
【提交】提交Cache_20180500022.c,其中后面的数字为你的学号。 你提交的文件,将会被复制到特定的机器上运行评分。
【评分】你的Cache.c,将会运行gedit.trace.zst,并进行评分。
二、代码框架
附在资源里啦
三、Data Cache的分析与见解
0、总体分析
原代码框架已经实现了一个直接映射的Cache,你的目标就是如何把它修改成组相联的Cache,并尽可能提高命中率!!!针对原直接映射的代码想做详细了解的可以看这篇文,建议先把原代码搞懂了再回来做直接映射Cache模拟器_matlab实现cache直接映射模拟器-优快云博客
另外,0️⃣基础的同学请自行查阅课本,了解相关知识,以及C、E、B、S、b、s、t、m等等固定的参数在Cache里面表示的是什么含义
解决思路是酱紫的:
1、GET_POWER_OF_2(X)用来干嘛???
///
Copyright 2022 by mars. //
///
#include <stdio.h>
#include <stdlib.h>
#include "common.h"
#define DEBUG 0
#define GET_POWER_OF_2(X) (X == 0x00 ? 0 : \
X == 0x01 ? 0 : \
X == 0x02 ? 1 : \
X == 0x04 ? 2 : \
X == 0x08 ? 3 : \
X == 0x10 ? 4 : \
X == 0x20 ? 5 : \
X == 0x40 ? 6 : \
X == 0x80 ? 7 : \
X == 0x100 ? 8 : \
X == 0x200 ? 9 : \
X == 0x400 ? 10 : \
X == 0x800 ? 11 : \
X == 0x1000 ? 12 : \
X == 0x2000 ? 13 : \
X == 0x4000 ? 14 : \
X == 0x8000 ? 15 : \
X == 0x10000 ? 16 : \
X == 0x20000 ? 17 : \
X == 0x40000 ? 18 : \
X == 0x80000 ? 19 : \
X == 0x100000 ? 20 : \
X == 0x200000 ? 21 : \
X == 0x400000 ? 22 : \
X == 0x800000 ? 23 : \
X == 0x1000000 ? 24 : \
X == 0x2000000 ? 25 : \
X == 0x4000000 ? 26 : \
X == 0x8000000 ? 27 : \
X == 0x10000000 ? 28 : \
X == 0x20000000 ? 29 : \
X == 0x40000000 ? 30 : \
X == 0x80000000 ? 31 : \
X == 0x100000000 ? 32 : 0)
这一部分没什么好说的,GET_POWER_OF_2(X)是为了获得
2、一些宏定义
/*
直接映射Data Cache,16KB大小
每行存放64个字节,共256行
*/
#define DCACHE_SIZE 16384 /*----题目要求的Cache必须是16KB----*/
#define DCACHE_DATA_PER_LINE 16 /*----B:一行多少数据字节?----*/ // 必须是8字节的倍数
#define DCACHE_DATA_PER_LINE_ADDR_BITS GET_POWER_OF_2(DCACHE_DATA_PER_LINE) /*---- b:即addr得有几位?----*/ // 必须与上面设置一致,即64字节,需要6位地址
#define DCACHE_SET (DCACHE_SIZE/DCACHE_DATA_PER_LINE)/*----S(组数): S*B=C----*/
#define DCACHE_SET_ADDR_BITS GET_POWER_OF_2(DCACHE_SET) /*----s需要多少位?----*/ // 必须与上面设置一致,即256行,需要8位地址
每个参量是什么意思,我写在每行的注释上了:/*----#@¥%&----*/我写的注释都会以这种格式出现
在这里也展示了GET_POWER_OF_2(X)的用处
这段声明了直接映射Cache的C、B、S、b、s
那么我们应该怎么仿照这个去写组相联呢?
3、需要添加的宏定义
//---------------------------------------
#define LINE_PER_GROUP 64 /*----组相联更新,每组多少行?----*/
#define GROUP_NUM (DCACHE_SET/LINE_PER_GROUP) /*----有几组:新的S,相当于原来的DCACHE_SET----*/
#define GROUP_ADDR_BITS GET_POWER_OF_2(GROUP_NUM)
//---------------------------------------
做一个说明:被这个框选的 //--------------------------------------- 都是你需要修改、添加进去的
组相联需要E,所以第一个就需要补充它,另外根据它更新S、s
4、结构体怎么改?
// Cache行的结构,包括Valid、Tag和Data。你所有的状态信息,只能记录在Cache行中!
struct DCACHE_LineStruct
{
UINT8 Valid; /*----有效位----*/
UINT64 Tag; /*----标记----*/
UINT8 Data[DCACHE_DATA_PER_LINE]; /*----数据组----*/
//---------------------------------------
UINT8 Dirty; /*----修改过没有----*/
UINT32 LRU; /*----最近最新标记(访问次数)----*/
//---------------------------------------
}DCache[DCACHE_SET];
//---------------------------------------
UINT32 num = 0;
//---------------------------------------
这个结构体是针对Cache的每一行缓存做的定义,原来已经标好了有效位(8位无符号整数),标记Tag(64位无符号整数,是组相联里面用来识别具体读写哪一行的关键!!!⚠️),数据组Data(B个字节,4B位无符号整数)
除原来的代码之外,还需要更新Dirty用来记录改行数据修改过没有:dirty标志用来表示数据有没有被CPU修改。当dirty表示为1时,表示Cache的内容被修改,和内存的内容不一致,当该Cache line被移除时,数据需要被更新到内存,dirty表示为0(称为clean),表示Cache的内容和内存的内容一致(操作系统是这么定义的 Dirty Bit: Indicating whether the page has been modified since it was brought into memory);LRU用来标记访问次数,LRU相关知识可以从这篇文了解全面讲解LRU算法-优快云博客
在这里可能会质疑:为什么DCache[DCACHE_SET]而不是DCache[GROUP_NUM],因为组相联的S已经改变了啊???,注意,这是行结构,我的Data Cache代码整体并没有真正的模仿组相联的实际结构,而是利用了最开始直接映射的大体框架,我更新出来新的S和E,只不过是为了在行的基础上更快速的索引到对应的组,当然你也可以表示成DCache[GROUP_NUM],然后在构造出一个有E个组的数组(不过在写的时候我觉得emmmm……,这样会降低实际索引的效率,不利于空间局部性的利用)不过后面的Inst Cache就是用二维数组写的
另外需要加一个32位无符号整数num来记录最近访问过没有,也就是每一行在读写到Cache时,LRU这个参数应该是什么值
5、InitDataCache()
/*
DCache初始化代码,一般需要把DCache的有效位Valid设置为0
模拟器启动时,会调用此InitDataCache函数
*/
void InitDataCache()
{
UINT32 i;
printf("[%s] +---------------------------------------+\n", __func__);
printf("[%s] | 狂魔作业王的Data Cache初始化ing.... |\n", __func__);
printf("[%s] +---------------------------------------+\n", __func__);
for (i = 0; i < DCACHE_SET; i++) {
DCache[i].Valid = 0;
//---------------------
DCache[i].LRU = 0;
DCache[i].Dirty = 0; /*----初始化的时候全部置零----*/
}
num = 0;
//--------------------
}
在原初始化整个Cache的函数里面,把我们新添加的LRU和Dirty两个参量也置零,另外别忘记num也置零!!!
6、LoadDataCacheLineFromMemory()、StoreDataCacheLineToMemory()
/*
从Memory中读入一行数据到Data 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;
}
}
/*
将Data 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);
}
}
因为我在前面说的,我并没有实际上改变cache的结构,所以读入和写入的这两个函数并不需要做任何修改,和直接映射cache一样就可以。这里做一些解释: Address: 要加载数据的目标内存地址 ;CacheLineAddress: 缓存中对应的数据行地址;循环遍历缓存行中的每个8字节(64位)数据,直到填充完整个缓存行,写入也是一样的
7、Choose() 🌟LRU替换策略的实现🌟
//----------------------------------------------
/*----LRU替换选择----*/
UINT32 Choose(UINT32 Cache_match_group_addr)
{
UINT32 Cache_group_line_addr = Cache_match_group_addr * LINE_PER_GROUP;
UINT32 min = DCache[Cache_group_line_addr].LRU;
UINT32 min_lineaddress = Cache_group_line_addr;
while (Cache_group_line_addr < (Cache_match_group_addr + 1) * LINE_PER_GROUP) {
if (DCache[Cache_group_line_addr].Valid == 0) {
return Cache_group_line_addr;
}
if (DCache[Cache_group_line_addr].LRU < min) {
min = DCache[Cache_group_line_addr].LRU;
min_lineaddress = Cache_group_line_addr;
}
Cache_group_line_addr++;
}
return min_lineaddress;
}
//----------------------------------------------
为了提高组相联命中率我们采用了LRU实现,理所应当的我们需要写一个实现函数,这整个函数都是需要自己补进去的。Cache_match_group_addr表示的是该行应该出现在哪个组里,即s,Cache_group_line_addr表示这个组的第0行地址,min来记录这个组里哪一行的LRU最小,最小的那行就应该是我们替换的行,min_lineaddress表示要替换的行的地址,初始化为第0行的地址,while (Cache_group_line_addr < (Cache_match_group_addr + 1) * LINE_PER_GROUP)这行代码是为了保证我们在遍历这一组所有行的时候不要超出了这一组的地址,最终返回我们判断出的应该替换的行的地址
8、AccessDataCache()主体实现
/*
Data Cache访问接口,系统模拟器会调用此接口,来实现对你的Data Cache访问
Address: 访存字节地址
Operation: 操作:读操作('L')、写操作('S')、读-修改-写操作('M')
DataSize: 数据大小:1字节、2字节、4字节、8字节
StoreValue: 当执行写操作的时候,需要写入的数据
LoadResult: 当执行读操作的时候,从Cache读出的数据
*/
UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, UINT64 StoreValue, UINT64* LoadResult)
{
UINT32 Cache_group_line_addr;
UINT8 BlockOffset;
UINT64 AddressTag;
UINT8 MissFlag = 'M';
UINT64 ReadValue;
//---------------
UINT32 Cache_match_group_addr;
UINT8 flag = 0;
num++;
//---------------
*LoadResult = 0;
/*
* 直接映射中,Address被切分为 AddressTag,CacheLineAddress,BlockOffset
*/
// Cache_group_line_addr Cache的行号,在直接映射中,就是组号(每组1行)
/*Cache_group_line_addr = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) % DCACHE_SET;*/
//------------------------------------------
/*----获取指令中的组号----*/
Cache_match_group_addr = (Address >> (DCACHE_DATA_PER_LINE_ADDR_BITS)) % GROUP_NUM;
//-----------------------------------------
/*----获取读取的字节标号----*/
BlockOffset = Address % DCACHE_DATA_PER_LINE;
/*----获取Tag----*/
//AddressTag = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) >> DCACHE_SET_ADDR_BITS; // 地址去掉DCACHE_SET、DCACHE_DATA_PER_LINE,剩下的作为Tag。警告!不能将整个地址作为Tag!!
//------------------------------
AddressTag = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) >> GROUP_ADDR_BITS;
for (int i = 0; i < LINE_PER_GROUP; i++)
{
Cache_group_line_addr = Cache_match_group_addr * LINE_PER_GROUP + i;
if (DCache[Cache_group_line_addr].Valid == 1 && DCache[Cache_group_line_addr].Tag == AddressTag)
{
flag = 1;
break;
}
}
//--------------------------------
/*if (DCache[Cache_group_line_addr].Valid == 1 && DCache[Cache_group_line_addr].Tag == AddressTag)*/
//------------------
if (flag==1)
{
DCache[Cache_group_line_addr].LRU = num;
//------------------
MissFlag = 'H'; // 命中!
if (Operation == 'L') // 读操作
{
ReadValue = 0;
switch (DataSize)
{
case 1: // 1个字节
ReadValue = DCache[Cache_group_line_addr].Data[BlockOffset + 0];
break;
case 2: // 2个字节
BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界
ReadValue = DCache[Cache_group_line_addr].Data[BlockOffset + 1]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 0];
break;
case 4: // 4个字节
BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界
ReadValue = DCache[Cache_group_line_addr].Data[BlockOffset + 3]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 2]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 1]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 0];
break;
case 8: // 8个字节
BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界
ReadValue = DCache[Cache_group_line_addr].Data[BlockOffset + 7]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 6]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 5]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 4]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 3]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 2]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 1]; ReadValue = ReadValue << 8;
ReadValue |= DCache[Cache_group_line_addr].Data[BlockOffset + 0];
break;
}
*LoadResult = ReadValue;
if (DEBUG)
printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX ReadValue=%016llX\n", __func__, Address, Operation, DataSize, StoreValue, ReadValue);
}
else if (Operation == 'S' || Operation == 'M') // 写操作(修改操作在此等价于写操作)
{
if (DEBUG)
printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX\n", __func__, Address, Operation, DataSize, StoreValue);
switch (DataSize)
{
case 1: // 1个字节
DCache[Cache_group_line_addr].Data[BlockOffset + 0] = StoreValue & 0xFF;
break;
case 2: // 2个字节
BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界
DCache[Cache_group_line_addr].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 1] = StoreValue & 0xFF;
break;
case 4: // 4个字节
BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界
DCache[Cache_group_line_addr].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 3] = StoreValue & 0xFF;
break;
case 8: // 8个字节
BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界
DCache[Cache_group_line_addr].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 3] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 4] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 5] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 6] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 7] = StoreValue & 0xFF;
break;
}
}
}
else
{
if (DEBUG)
printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX\n", __func__, Address, Operation, DataSize, StoreValue);
MissFlag = 'M'; // 不命中
//---------------------------
Cache_group_line_addr = 0;
UINT32 Find_address = Cache_match_group_addr * LINE_PER_GROUP-1;
for (UINT32 i = 0; i < LINE_PER_GROUP; i++)
{
Find_address++;
if (DCache[Find_address].Valid == 0)
{
Cache_group_line_addr = Find_address;
break;
}
}
if (Cache_group_line_addr == 0) Cache_group_line_addr = Choose(Cache_match_group_addr);
DCache[Cache_group_line_addr].LRU = num;
//----------------------------
if (DCache[Cache_group_line_addr].Valid == 1)
{
// 淘汰对应的Cache行,如果对应的Cache行有数据,需要写回到Memory中
UINT64 OldAddress;
// OldAddress = > (Tag,Set,0000)
/*OldAddress = ((DCache[Cache_group_line_addr].Tag << DCACHE_SET_ADDR_BITS) << DCACHE_DATA_PER_LINE_ADDR_BITS) | ((UINT64)Cache_group_line_addr << DCACHE_DATA_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址*/
//-----------------------------------
OldAddress = ((DCache[Cache_group_line_addr].Tag << GROUP_ADDR_BITS) << DCACHE_DATA_PER_LINE_ADDR_BITS) | ((UINT64)Cache_match_group_addr << DCACHE_DATA_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址
//-----------------------------------
StoreDataCacheLineToMemory(OldAddress, Cache_group_line_addr);
}
// 需要从Memory中读入新的行(真实情况下,这个LoadCacheLineFromMemory需要很长时间的)
LoadDataCacheLineFromMemory(Address, Cache_group_line_addr);
DCache[Cache_group_line_addr].Valid = 1;
DCache[Cache_group_line_addr].Tag = AddressTag;
if (Operation == 'L') // 读操作
{
// 读操作不需要做事情,因为已经MISS了
}
else if (Operation == 'S' || Operation == 'M') // 写操作(修改操作在此等价于写操作)
{
// 写操作,需要将新的StoreValue更新到CacheLine中
switch (DataSize)
{
case 1: // 1个字节
DCache[Cache_group_line_addr].Data[BlockOffset + 0] = StoreValue & 0xFF;
break;
case 2: // 2个字节
BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界
DCache[Cache_group_line_addr].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 1] = StoreValue & 0xFF;
break;
case 4: // 4个字节
BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界
DCache[Cache_group_line_addr].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 3] = StoreValue & 0xFF;
break;
case 8: // 8个字节
BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界
DCache[Cache_group_line_addr].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 3] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 4] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 5] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 6] = StoreValue & 0xFF; StoreValue = StoreValue >> 8;
DCache[Cache_group_line_addr].Data[BlockOffset + 7] = StoreValue & 0xFF;
break;
}
}
}
return MissFlag;
}
Data Cache访问接口的实现,一定仔细理解原代码,还有注释里的这几个参量表示什么意思,
Address: 访存字节地址 ➡️ 组相联映射:主存物理地址=标记+组号+块内地址
Operation: 操作:读操作('L')、写操作('S')、读-修改-写操作('M')
DataSize: 数据大小:1字节、2字节、4字节、8字节
StoreValue: 当执行写操作的时候,需要写入的数据
LoadResult: 当执行读操作的时候,从Cache读出的数据
1️⃣需要添加声明,后面会用到
首先你需要添加三个声明,这个后面会做解释:
//---------------
UINT32 Cache_match_group_addr;
UINT8 flag = 0;
num++;
//---------------
2️⃣组号的修改
把原先的Cache_group_line_addr这一行注释掉,因为组号s变了!!!更换成下面加粗的这一行
// Cache_group_line_addr Cache的行号,在直接映射中,就是组号(每组1行)
/*Cache_group_line_addr = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) % DCACHE_SET;*/
//------------------------------------------
/*----获取指令中的组号----*/
Cache_match_group_addr = (Address >> (DCACHE_DATA_PER_LINE_ADDR_BITS)) % GROUP_NUM;
//------------------------------------------
3️⃣偏移量
//------------------------------------------
/*----获取读取的字节标号(偏移量)----*/
BlockOffset = Address % DCACHE_DATA_PER_LINE;
//------------------------------------------
4️⃣Tag标记
/*----获取Tag----*/
//AddressTag = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) >> DCACHE_SET_ADDR_BITS; // 地址去掉DCACHE_SET、DCACHE_DATA_PER_LINE,剩下的作为Tag。警告!不能将整个地址作为Tag!!
//------------------------------------------
AddressTag = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) >> GROUP_ADDR_BITS;
//------------------------------------------
因为:直接映射:主存物理地址=标记+cache行号+块内地址 组相联映射:主存物理地址=标记+组号+块内地址
所以在原来基础上把>> DCACHE_SET_ADDR_BITS修改为>> GROUP_ADDR_BITS
5️⃣是否命中
//--------------------------------
for (int i = 0; i < LINE_PER_GROUP; i++)
{
Cache_group_line_addr = Cache_match_group_addr * LINE_PER_GROUP + i;
if (DCache[Cache_group_line_addr].Valid == 1 && DCache[Cache_group_line_addr].Tag == AddressTag)
{
flag = 1;
break;
}
}
//--------------------------------
开始遍历匹配组里的所有行,如果命中就将flag置为1,反之为0
6️⃣LRU值的更新
/*if (DCache[Cache_group_line_addr].Valid == 1 && DCache[Cache_group_line_addr].Tag == AddressTag)*/
//------------------
if (flag==1)
{
DCache[Cache_group_line_addr].LRU = num;
//------------------
如果命中了,就将LRU赋值为num(后面的不命中也会赋值,从这里可以显然看出,如果最近被访问过,那么这行的LRU就会更大,因为AccessDataCache函数在1️⃣里就num++了)
然后后面就是原先的命中的读操作和写操作,不需要做修改,不过建议从我前面说的这篇文章里面理解一下直接映射Cache模拟器_matlab实现cache直接映射模拟器-优快云博客
主要就是通过datasize,到底是一个一个字节读写还是两个两个字节读写,把offset对齐,和前面的LoadDataCacheLineFromMemory、StoreDataCacheLineToMemory两个函数里的地址对齐一个意思
7️⃣没命中,找替换行
//---------------------------
Cache_group_line_addr = 0;
UINT32 Find_address = Cache_match_group_addr * LINE_PER_GROUP-1;
for (UINT32 i = 0; i < LINE_PER_GROUP; i++)
{
Find_address++;
if (DCache[Find_address].Valid == 0)
{
Cache_group_line_addr = Find_address;
break;
}
}
if (Cache_group_line_addr == 0) Cache_group_line_addr = Choose(Cache_match_group_addr);
DCache[Cache_group_line_addr].LRU = num;
//----------------------------
在else后面,也就是不命中的情况下,同样需要补充一段代码,因为不命中的话,需要根据LRU找到要替换的行!!!注意⚠️如果存在Valid无效的行,优先替换无效行,当没有无效行时,再根据LRU找到最少访问的行作为替换行
8️⃣删除替换行时要写回memory
if (DCache[Cache_group_line_addr].Valid == 1)
{
// 淘汰对应的Cache行,如果对应的Cache行有数据,需要写回到Memory中
UINT64 OldAddress;
// OldAddress = > (Tag,Set,0000)
/*OldAddress = ((DCache[Cache_group_line_addr].Tag << DCACHE_SET_ADDR_BITS) << DCACHE_DATA_PER_LINE_ADDR_BITS) | ((UINT64)Cache_group_line_addr << DCACHE_DATA_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址*/
//-----------------------------------
OldAddress = ((DCache[Cache_group_line_addr].Tag << GROUP_ADDR_BITS) << DCACHE_DATA_PER_LINE_ADDR_BITS) | ((UINT64)Cache_match_group_addr << DCACHE_DATA_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址
//-----------------------------------
StoreDataCacheLineToMemory(OldAddress, Cache_group_line_addr);
}
注意这一部分需要修改OldAddress,原先的行号修改成组号
注意这行紫色的代码,正是因为它,每次替换行的时候都有store到memory里面,所以,其实dirty位我们并没有真的使用到!!!你可以通过置为1判断这一行修改过,现在要被删除了,所以要把它存储到memory里面(主要是我懒,因为有现成的……懒得改了hhhh)不过为了看起来组相联的结构更完整一些,所以仍然加入了dirty位,尽管没用到
后面就和命中时的读写差不多了,不做解释了(码字真的好累💤)
四、关于命令行检测太慢怎么变得飞快!!!
1️⃣命令行输入:
./MyCache.exe -w ./traces/gedit.trace.bin.zst
2️⃣然后:
./MyCache.exe -r ./traces/gedit.trace.bin.zst
你就会发现,这比只输入./MyCache.exe ./traces/dave.trace.zst快得多!!!
五、Inst Cache的分析与见解(选做部分)
1、更新的宏定义
/*----Ins Cache???其实就是把Data换成了Instruct,仿照前面的组相联来一个就OK----*/
#define ICACHE_SIZE 16384
#define ICACHE_LINE_PER_SET 64 /*----E:每组多少行?----*/
#define ICACHE_DATA_PER_LINE 256 /*----B:每行多少数据?----*/
#define ICACHE_SET (ICACHE_SIZE/ICACHE_DATA_PER_LINE/ICACHE_LINE_PER_SET) /*----S:多少组----*/
#define ICACHE_DATA_PER_LINE_ADDR_BITS GET_POWER_OF_2(ICACHE_DATA_PER_LINE)/*----b----*/
#define ICACHE_SET_ADDR_BITS GET_POWER_OF_2(ICACHE_SET)/*----s----*/
//---------------------------------------
这和前面一样,照着葫芦画瓢画一个就好E、B、S、b、s
2、Inst Cache结构体
struct ICACHE_LineStruct
{
UINT32 Life[ICACHE_LINE_PER_SET];/*----生命值:Life 数组用于实现最近最少使用(LRU)缓存替换策略。
每个缓存行都有一个与之关联的生命值,用于记录该行自上次被访问以来经过的时间。
当需要替换缓存行时,选择具有最长生命值的行进行替换*/
UINT8 Valid[ICACHE_LINE_PER_SET];/*----有效位组----*/
UINT64 Tag[ICACHE_LINE_PER_SET];/*----标记组----*/
UINT8 Data[ICACHE_LINE_PER_SET][ICACHE_DATA_PER_LINE];/*----数据组【行】【列】----*/
}ICache[DCACHE_SET];
一样的声明结构体
生命值:Life 数组用于实现最近最少使用(LRU)缓存替换策略。每个缓存行都有一个与之关联的生命值,用于记录该行自上次被访问以来经过的时间。当需要替换缓存行时,选择具有最长生命值的行进行替换。(ps: 其实和LRU一样,只不过换了个说法~~)
在这里没有沿用直接映射的结构体了,而是用了前面说的二维数组,其实这个结构体叫ICACHE_GroupStruct更好,懒得改了(芜湖~亚比囧囧囧)
3、Inst Cache初始化
/* 指令Cache实现部分,可选实现 */
void InitInstCache(void)
{
UINT32 i, j;
printf("[%s] +------------------------------------------------------+\n", __func__);
printf("[%s] | 疯狂作业王的64组全相联Instruct Cache初始化ing.... |\n", __func__);
printf("[%s] +------------------------------------------------------+\n", __func__);
for (i = 0; i < ICACHE_SET; i++)
{
for (j = 0; j < ICACHE_LINE_PER_SET; j++)
{
ICache[i].Valid[j] = 0;
ICache[i].Life[j] = 0xffff;/*----有效位置零,生命值置最大----*/
}
}
return;
}
有效位置零,生命值置最大
4、load函数,没有store函数
void LoadInstCacheLineFromMemory(UINT64 Address, UINT32 CacheSetAddress, UINT32 CacheLineOffset)
{
// 一次性从Memory中将DCACHE_DATA_PER_LINE数据读入某个Data Cache行
// 提供了一个函数,一次可以读入8个字节
UINT32 i;
UINT64 ReadData;
UINT64 AlignAddress;
UINT64* pp;
AlignAddress = Address & ~(ICACHE_DATA_PER_LINE - 1); // 地址必须对齐到DCACHE_DATA_PER_LINE (64)字节边界
pp = (UINT64*)ICache[CacheSetAddress].Data[CacheLineOffset];
for (i = 0; i < ICACHE_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;
}
return;
}
5、Inst Cache主体实现
UINT8 AccessInstCache(UINT64 Address, UINT8 Operation, UINT8 InstSize, UINT64* InstResult)
{
// 返回值'M' = Miss,'H'=Hit
UINT32 CacheSetAddress;
UINT32 CacheLineOffset;
UINT8 BlockOffset;
UINT64 AddressTag;
UINT8 MissFlag = 'M';
UINT64 ReadValue;
*InstResult = 0;
/*
* 直接映射中,Address被切分为 AddressTag,CacheSetAddress,BlockOffset
*/
// CacheSetAddress Cache的行号,在直接映射中,就是组号(每组1行)
CacheSetAddress = (Address >> ICACHE_DATA_PER_LINE_ADDR_BITS) % ICACHE_SET;
BlockOffset = Address % ICACHE_DATA_PER_LINE;
AddressTag = (Address >> ICACHE_DATA_PER_LINE_ADDR_BITS) >> ICACHE_SET_ADDR_BITS; // 地址去掉DCACHE_SET、DCACHE_DATA_PER_LINE,剩下的作为Tag。警告!不能将整个地址作为Tag!!
for (CacheLineOffset = 0; CacheLineOffset < ICACHE_LINE_PER_SET; CacheLineOffset++)
{
if (ICache[CacheSetAddress].Valid[CacheLineOffset] == 1 && ICache[CacheSetAddress].Tag[CacheLineOffset] == AddressTag)
goto AccessInst;
}
for (CacheLineOffset = 0; CacheLineOffset < ICACHE_LINE_PER_SET; CacheLineOffset++)
{
if (ICache[CacheSetAddress].Valid[CacheLineOffset] == 0)
goto AccessInst;
}
UINT32 min = ICache[CacheSetAddress].Life[CacheLineOffset];
UINT32 temp = 0;
for (CacheLineOffset = 1; CacheLineOffset < ICACHE_LINE_PER_SET; CacheLineOffset++)
{
if (min > ICache[CacheSetAddress].Life[CacheLineOffset])
{
min = ICache[CacheSetAddress].Life[CacheLineOffset];
temp = CacheLineOffset;
}
}
CacheLineOffset = temp;
AccessInst:
UINT16 i;
for (i = 0; i < ICACHE_LINE_PER_SET; i++)
{
ICache[CacheSetAddress].Life[i]--;
}
if (ICache[CacheSetAddress].Valid[CacheLineOffset] == 1 && ICache[CacheSetAddress].Tag[CacheLineOffset] == AddressTag)
{
MissFlag = 'H'; // 命中!
ICache[CacheSetAddress].Life[CacheLineOffset] = 0xffff; // 生命值置最大
ReadValue = 0;
switch (InstSize)
{
case 1: // 1个字节
ReadValue = ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 0];
break;
case 2: // 2个字节
BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界
ReadValue = ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 1]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 0];
break;
case 4: // 4个字节
BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界
ReadValue = ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 3]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 2]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 1]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 0];
break;
case 8: // 8个字节
BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界
ReadValue = ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 7]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 6]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 5]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 4]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 3]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 2]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 1]; ReadValue = ReadValue << 8;
ReadValue |= ICache[CacheSetAddress].Data[CacheLineOffset][BlockOffset + 0];
break;
}
*InstResult = ReadValue;
if (DEBUG)
printf("[%s] Address=%016llX Operation=%c InstSize=%u ReadValue=%016llX\n", __func__, Address, Operation, InstSize, ReadValue);
}
else
{
if (DEBUG)
printf("[%s] Address=%016llX Operation=%c InstSize=%u\n", __func__, Address, Operation, InstSize);
MissFlag = 'M'; // 不命中
ICache[CacheSetAddress].Life[CacheLineOffset] = 0xffff; // 更新后生命值置最大
// 需要从Memory中读入新的行(真实情况下,这个LoadCacheLineFromMemory需要很长时间的)
LoadInstCacheLineFromMemory(Address, CacheSetAddress, CacheLineOffset);
ICache[CacheSetAddress].Valid[CacheLineOffset] = 1;
ICache[CacheSetAddress].Tag[CacheLineOffset] = AddressTag;
if (Operation == 'L') // 读操作
{
// 读操作不需要做事情,因为已经MISS了
}
}
return MissFlag;
//return 'M';
}
前面用了两个goto,如果命中就进入读写,如果找到无效位就进入读写,那就只剩下一种情况了,Valid全部是1而且没有命中🎯,那就需要和前面LRU一样,找到Life最大的做替换行,另外,因为改行刚刚发生过load所以Life直接置0。
另外,关于为什么要减少命中的整个组内所有行的“生命值”,因为后面对于读写操作都是将life置为最大值,而没有出现过生命值减小,如果不这样做,最后可能会出现所有行的生命值都是最大值,那替换将变得没意义了,所以对命中的组内所有行的生命值都-1,然后再把命中行的生命值置最大,就能体现出最近是否被访问过的差距。
因为命令没有写,所以这一部分函数相较于Data Cache也简单了很多!!!