直接映射Cache模拟器

本文详细介绍了直接映射Cache的基本结构和工作原理,包括Cache的大小和组织方式,以及读写操作的处理流程。通过实例展示了如何进行地址切分、命中与未命中的操作处理,以及数据的读取和写回策略。同时,还提供了从内存加载和存储数据到Cache的Load和Store函数实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

直接映射Cache模拟器

直接映射是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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值