第一次没过破防了决定直接一部到胃一起写必做一和选做一

1. 概述

  1. 根据指导书可知,我们需要实现一级高速缓存和二级告诉缓存,其中l2会多一个脏标签的处理
  2. 公式:C=B* E *S (E缓存块 B字节 S组 C总大小)
    1. l1: E=8 B=64 C=64*1024 S=128
    2. l2: E=8 B=64 C=4*1024 *1024 S=4096
  3. Address:
    1. tag: t bits
    2. set index: s bits
    3. block offset: b bits
    1. l1: b=6 s=7
    2. l2: b=6 s=12

2. 实现

  1. 定义高速缓存的结构(nemu/include/memory/cache.h)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdint.h>

#define CACHE_b 6 //b
#define CACHE_L1_e 3 //一级高速缓存的e
#define CACHE_L1_s 7 //一级高速缓存的s
#define CACHE_L2_e 4 //二级高速缓存的e
#define CACHE_L2_s 12 //二级高速缓存的s
#define CACHE_L1_CAP (64 * 1024) //一级高速缓存的C
#define CACHE_L2_CAP (4 * 1024 * 1024) //二级高速缓存的C
//通过对1进行右移小写字母位的操作对应的就是转化为大写字母的过程(*2)
#define CACHE_B (1 << CACHE_b)
#define CACHE_L1_E (1 << CACHE_L1_e)
#define CACHE_L1_S (1 << CACHE_L1_s)
#define CACHE_L2_E (1 << CACHE_L2_e)
#define CACHE_L2_S (1 << CACHE_L2_s)

//一级高速缓存L1的定义
typedef struct{
uint8_t data[CACHE_B]; //字节数组存储块内数据
uint32_t tag; //标签位
bool validVal; //有效位
} L1;
//缓存块数组表示整个一级高速缓存
L1 cache_L1[CACHE_L1_S * CACHE_L1_E];

//二级高速缓存L2的定义,与一级高速缓存相比多了一个脏标签
typedef struct{
uint8_t data[CACHE_B];
uint32_t tag;
bool validVal;
bool dirtyVal;
} L2;

L2 cache_L2[CACHE_L2_S * CACHE_L2_E];
  1. 实现相关函数(nemu/src/memory/cache.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//初始化高速缓存
void init_cache() {
//遍历所有高速缓存块,将有效位和脏标签清空即可
int i;
for (i = 0; i < CACHE_L1_S * CACHE_L1_E; i++) {
cache_L1[i].validVal = false;
}
for (i = 0; i < CACHE_L2_S * CACHE_L2_E; i++) {
cache_L2[i].dirtyVal = false;
cache_L2[i].validVal = false;
}
return;
}

//一级高速缓存
//声明需要调用的外部函数,实现对内存种数据的读取
void ddr3_read_me(hwaddr_t addr, void* data);

//查找地址对应的数据是否存在于一级高速缓存中,返回数据块位于缓存中的位置参数
int read_cache_L1(hwaddr_t addr) {
uint32_t set = ((addr >> CACHE_b) & (CACHE_L1_S - 1)); //获取组索引参数
uint32_t tag = (addr >> (CACHE_b + CACHE_L1_s)); //获取标签参数
//遍历组内每一个缓存块进行匹配
int block_i;
int set_begin = set * CACHE_L1_E;
int set_end = (set + 1) * CACHE_L1_E;
for (block_i = set_begin; block_i < set_end; block_i++)
if (cache_L1[block_i].validVal && cache_L1[block_i].tag == tag) // Hit!
return block_i;
//若一级高速缓存内未命中,则到二级高速缓存中查找
srand(time(0));
int block_i_L2 = read_cache_L2(addr);
//若二级缓存内未命中,则查找一级高速缓存中是否存在空的缓存块
for (block_i = set_begin; block_i < set_end; block_i++)
if (!cache_L1[block_i].validVal)
break;
//若无空缓存块,则采用随机替换策略,通过随机生成数在组内找一个缓存块进行替换
if (block_i == set_end)
block_i = set_begin + rand() % CACHE_L1_E;
//写入数据到替换好的缓存块中,从二级高速缓存中读取,已经读入了二级高速缓存中
memcpy(cache_L1[block_i].data, cache_L2[block_i_L2].data, CACHE_B);

cache_L1[block_i].validVal = true;
cache_L1[block_i].tag = tag;
return block_i;
}

//查找二级缓存
//声明对内存进行写操作的外部函数
void ddr3_write_me(hwaddr_t addr, void* data, uint8_t* mask);

// 查找二级高速缓存中与地址匹配的缓存块,返回缓存块的位置参数
int read_cache_L2(hwaddr_t addr) {
uint32_t set = ((addr >> CACHE_b) & (CACHE_L2_S - 1));
uint32_t tag = (addr >> (CACHE_b + CACHE_L2_s));
//涉及到块内写操作,要遵循空间局部性原理,清空块内偏移量,找到块的起始位置
uint32_t block_start = ((addr >> CACHE_b) << CACHE_b);
int block_i;
int set_begin = set * CACHE_L2_E;
int set_end = (set + 1) * CACHE_L2_E;
for (block_i = set_begin; block_i < set_end; block_i++)
if (cache_L2[block_i].validVal && cache_L2[block_i].tag == tag)
return block_i;
//若二级缓存中没有找到,则查找是否存在空的缓存块
srand(time(0));
for (block_i = set_begin; block_i < set_end; block_i++)
if (!cache_L2[block_i].validVal)
break;
//若无空缓存块,则采用随机替换策略,通过随机生成数在组内找一个缓存块进行替换
if (block_i == set_end)
block_i = set_begin + rand() % CACHE_L2_E;
//随机替换找的缓存块先确定其是否脏标签被置位,若是,需要先进行回写操作
int i;
if (cache_L2[block_i].validVal && cache_L2[block_i].dirtyVal) {
//临时缓冲区结合突发读写技术读取需要写入的数据
uint8_t tmp[BURST_LEN << 1];
memset(tmp, 1, sizeof(tmp));
uint32_t block_start_x = (cache_L2[block_i].tag << (CACHE_L2_s + CACHE_b)) | (set << CACHE_b);
for (i = 0; i < CACHE_B / BURST_LEN; i++) {
ddr3_write_me(block_start_x + BURST_LEN * i, cache_L2[block_i].data + BURST_LEN * i, tmp);
}
}
//处理好回写后替换块内数据,从内存中写入
for (i = 0; i < CACHE_B / BURST_LEN; i++) {
ddr3_read_me(block_start + BURST_LEN * i, cache_L2[block_i].data + BURST_LEN * i);
}
cache_L2[block_i].validVal = true;
cache_L2[block_i].dirtyVal = false;
cache_L2[block_i].tag = tag;
return block_i;
}
  1. 更改hwaddr_read()和hwaddr_write() (emu/src/memory/memory.c)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
uint32_t hwaddr_read(hwaddr_t addr, size_t len) {
int cache_L1_way_1_index = read_cache_L1(addr); // 在L1缓存中查找地址对应的缓存行索引
uint32_t block_bias = addr & (CACHE_B - 1); //计算块内偏移
uint8_t ret[BURST_LEN << 1];
if (block_bias + len > CACHE_B) {
// 需要读取两个缓存块
int cache_L1_way_2_index = read_cache_L1(addr + CACHE_B - block_bias);
memcpy(ret, cache_L1[cache_L1_way_1_index].data + block_bias, CACHE_B - block_bias);
// 单个块内读取
memcpy(ret + CACHE_B - block_bias, cache_L1[cache_L1_way_2_index].data, len - (CACHE_B - block_bias));
} else {
memcpy(ret, cache_L1[cache_L1_way_1_index].data + block_bias, len);
}
int tmp = 0;
uint32_t result = unalign_rw(ret + tmp, 4) & (~0u >> ((4 - len) << 3)); // 处理非对齐访问
return result;
}

void hwaddr_write(hwaddr_t addr, size_t len, uint32_t data) {
write_cache_L1(addr, len, data);
}
  1. 由于被多次调用的读写操作函数位于dram.c文件中的,其中有两个函数是用static封装,则需在dram.c文件中进行全局变量的声明(作为接口调用这两个函数)
1
2
3
4
5
6
void ddr3_read_me(hwaddr_t addr, void* data) {
ddr3_read(addr, data) ;
}
void ddr3_write_me(hwaddr_t addr, void* data, uint8_t* mask) {
ddr3_write(addr, data, mask) ;
}

3. 调试

1
2
make clean
make run

通过即可