From 92300134d29686193f4bcde29ebbcaa1959ba1cf Mon Sep 17 00:00:00 2001 From: CGH0S7 <776459475@qq.com> Date: Thu, 24 Apr 2025 22:32:48 +0800 Subject: [PATCH] cachelab finished --- cachelab/Cache | Bin 0 -> 49312 bytes cachelab/Cache.bak | 1930 +++++++++++++++++++++++++++++++++++++++ cachelab/Cache.bak2 | 403 ++++++++ cachelab/Cache.c | 604 +++++++++--- cachelab/Makefile | 4 +- perflab/matrix/rowcol.c | 0 perflab/poly/a.exe | Bin 151145 -> 164791 bytes perflab/poly/poly.c | 0 8 files changed, 2793 insertions(+), 148 deletions(-) create mode 100755 cachelab/Cache create mode 100644 cachelab/Cache.bak create mode 100644 cachelab/Cache.bak2 create mode 100644 perflab/matrix/rowcol.c create mode 100644 perflab/poly/poly.c diff --git a/cachelab/Cache b/cachelab/Cache new file mode 100755 index 0000000000000000000000000000000000000000..7d65892c969abfb93c19d7e3721fcdfd443de422 GIT binary patch literal 49312 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 +#include "common.h" + +// debug=1:输出额外调试信息 +int debug = 0; + +#define MAX_AGE 3 + +// 组相联Data Cache +// 容量为16384字节,4路组相联,每行容纳16个字节 +#define DCACHE_SIZE 16384 +#define DCACHE_LINES_PER_SET 4 +#define DCACHE_BYTES_PER_LINE 16 // 必须是8字节的倍数 + +// 下面参数是自动计算的,你无须需改 +#define DCACHE_BYTES_PER_LINE_ADDR_BITS GET_LOG_OF_2(DCACHE_BYTES_PER_LINE) +#define DCACHE_SET (DCACHE_SIZE/(DCACHE_BYTES_PER_LINE*DCACHE_LINES_PER_SET)) +#define DCACHE_SET_ADDR_BITS GET_LOG_OF_2(DCACHE_SET) + +// Cache行的结构,包括Valid、Age、Tag和Data。你所有的状态信息,只能记录在Cache行中! +struct DCACHE_LineStruct +{ + UINT8 Valid; + UINT8 Age; + UINT64 Tag; + UINT8 Data[DCACHE_BYTES_PER_LINE]; +}; + +struct DCACHE_SetStruct { + struct DCACHE_LineStruct Line[4]; // 4路组相联Cache,每组有4行 +} DCache[DCACHE_SET]; + + +// DCache初始化代码,模拟器启动时,会调用此InitDataCache函数 +void InitDataCache() +{ + // 遍历每个组 + for (int set_idx = 0; set_idx < DCACHE_SET; set_idx++) { + struct DCACHE_SetStruct *set = &DCache[set_idx]; + // 遍历每组中的四个Cache行 + for (int line_idx = 0; line_idx < DCACHE_LINES_PER_SET; line_idx++) { + set->Line[line_idx].Valid = 0; // 有效位置0 + set->Line[line_idx].Age = line_idx; // 年龄依次设置为0、1、2、3 + } + } +} + +// 在第Set组中,从4路中,找到需要替换的Cache行 +int GetReplaceLine(UINT32 Set) +{ + struct DCACHE_SetStruct *set = &DCache[Set]; + int invalid_line = -1; + int max_age = -1; + int replace_line = 0; + + for (int i = 0; i < DCACHE_LINES_PER_SET; i++) { + struct DCACHE_LineStruct *line = &set->Line[i]; + if (line->Valid == 0) { + // 发现无效行,记录第一个找到的无效行 + if (invalid_line == -1) { + invalid_line = i; + } + } else { + // 更新最大年龄和对应的行号 + if (line->Age > max_age) { + max_age = line->Age; + replace_line = i; + } + } + } + + // 优先返回无效行,否则返回年龄最大的有效行 + return (invalid_line != -1) ? invalid_line : replace_line; +} + +// 更新Age,在第Set组中,将HitLine指定的Cache行的Age设置为0,其他行的Age要相应调整 +void UpdateAge(UINT32 Set, int HitLine) +{ + struct DCACHE_SetStruct *set = &DCache[Set]; + UINT8 old_age = set->Line[HitLine].Age; // 保存命中行原来的年龄 + set->Line[HitLine].Age = 0; // 命中行年龄置0 + + // 调整其他行的年龄 + for (int i = 0; i < DCACHE_LINES_PER_SET; i++) { + if (i == HitLine) continue; // 跳过命中行 + if (set->Line[i].Age < old_age) { + set->Line[i].Age += 1; // 原年龄小于命中行原年龄的加1 + } + } +} +// Data Cache访问接口,Cache模拟器会调用此接口,来实现对你的Data Cache访问 +// Address: 访存字节地址 +// Operation: 操作:读操作('L')、写操作('S')、读-修改-写操作('M') +// DataSize: 数据大小:1字节、2字节、4字节、8字节 +// StoreValue: 当执行写操作的时候,需要写入的数据 +// LoadResult: 当执行读操作的时候,从Cache读出的数据 +// 返回值:'M'表示Miss;'H'表示Hit +UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, + UINT64 StoreValue, UINT64* LoadResult) +{ + UINT32 Set; + UINT8 Block; + UINT64 Tag; + UINT8 MissFlag = 'M'; + UINT64 ReadValue; + int HitLine = 0; + UINT64 HitLineAddress = 0; + + *LoadResult = 0; + + + // Address被划分为 Tag + Set + Block + + // Set Cache的组索引(每组4行) + Set = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) % DCACHE_SET; + Block = Address % DCACHE_BYTES_PER_LINE; + Tag = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) >> DCACHE_SET_ADDR_BITS; // 地址去掉DCACHE_SET、DCACHE_BYTES_PER_LINE,剩下的作为Tag。警告!不能将整个地址作为Tag!! + + // 1. 首先需要检查对应的DCache行,其Valid位是否有效?Tag是否与AddressTag相等? + // 2. 如果Valid有效,且AddressTag相等,则意味着Cache访问“命中” + // 3. 如果“命中”,需要进一步判断是要读('L')还是写('S')或者是修改('M')? + MissFlag = 'M'; + for (int i = 0; i < DCACHE_LINES_PER_SET; i++) + { + if (DCache[Set].Line[i].Valid == 1 && DCache[Set].Line[i].Tag == Tag) + { + MissFlag = 'H'; + HitLine = i; + HitLineAddress = ((DCache[Set].Line[HitLine].Tag << DCACHE_SET_ADDR_BITS) << + DCACHE_BYTES_PER_LINE_ADDR_BITS) | ((UINT64)Set << + DCACHE_BYTES_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址 + break; + } + } + if (MissFlag == 'H') + { + if (Operation == 'L') // 读操作。从DCache对应行中,读取数据,注意数据宽度与对齐要求 + { + ReadValue = 0; + switch (DataSize) + { + case 1: // 1个字节 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 7]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 6]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 5]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 4]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 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[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 4] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 5] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 6] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 7] = StoreValue & 0xFF; + break; + } + // 写穿透:每次更新Cache行,都需要把数据同时写入Memory + StoreCacheLineToMemory(HitLineAddress, DCache[Set].Line[HitLine].Data, DCACHE_BYTES_PER_LINE); + } + UpdateAge(Set, HitLine); + } + else + { + if (debug) + printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX\n", + __func__, Address, Operation, DataSize, StoreValue); + if (Operation == 'L') // 读操作 + { + // 读操作不需要做事情,因为已经MISS了。Cache模拟器会直接从Memory中读取数据,而不需要DCache提供 + // 需要从Memory中读入新的行(真实情况下,这个LoadCacheLineFromMemory需要很长时间的) + HitLine = GetReplaceLine(Set); + LoadCacheLineFromMemory(Address, DCache[Set].Line[HitLine].Data, DCACHE_BYTES_PER_LINE); + DCache[Set].Line[HitLine].Valid = 1; + DCache[Set].Line[HitLine].Tag = Tag; + UpdateAge(Set, HitLine); + } + else if (Operation == 'S' || Operation == 'M') // 写操作(修改操作在此等价于写操作) + { + // 写操作,需要将新的StoreValue写到Memory中 + // 由于存储器访问每次都是8个字节,所以首先需要把旧的8个字节读取回来,然后根据需要更新 + UINT64 AlignAddress = Address & 0xFFFFFFFFFFFFFFF8; // 地址必须对齐到8字节边界 + UINT8 Offset; // 在8字节中的第几个字节? + UINT64 ReadData = (DataSize == 8) ? 0 : ReadMemory(AlignAddress); + UINT64 WriteData = ReadData; + switch (DataSize) + { + case 1: // 1个字节,要确定写入8个字节中的哪1个字节? + Offset = Address & 0x07; // 0~7 + WriteData = (ReadData & ~(0xFF<<8*Offset)) | ((StoreValue &0xFF) << 8*Offset); + break; + case 2: // 2个字节,要确定写入8个字节中的哪2个字节? + Offset = Address & 0x06; // 0、2、4、6 + WriteData = (ReadData & ~(0xFFFF<<8*Offset)) | ((StoreValue &0xFFFF) << 8*Offset); + break; + case 4: // 4个字节,要确定写入8个字节中的哪4个字节? + Offset = Address & 0x04; // 0、4 + WriteData = (ReadData & ~(0xFFFFFFFF << 8*Offset)) | ((StoreValue &0xFFFFFFFF) << 8*Offset); + break; + case 8: // 8个字节 + WriteData = StoreValue; + break; + } + WriteMemory(AlignAddress, WriteData); + // 写非分配。所以对于MISS的写操作,不在Cache中分配行 + } + } + return MissFlag; +} + +#ifdef ICACHE_ENABLE + +/* 指令Cache实现部分,可选实现 */ +void InitInstCache(void) +{ + return; +} + +UINT8 AccessInstCache(UINT64 Address, UINT8 Operation, UINT8 InstSize, + UINT64* InstResult) +{ + // ICache只有Operation = 'I'的操作,只会读。不会写 + // 返回值'M' = Miss,'H'=Hit + return 'M'; +} +#endif + +3 +#include +#include "common.h" + +// debug=1:输出额外调试信息 +int debug = 0; + +#define MAX_AGE 3 + +// 组相联Data Cache +// 容量为16384字节,4路组相联,每行容纳16个字节 +#define DCACHE_SIZE 16384 +#define DCACHE_LINES_PER_SET 4 +#define DCACHE_BYTES_PER_LINE 16 // 必须是8字节的倍数 + +// 下面参数是自动计算的,你无须需改 +#define DCACHE_BYTES_PER_LINE_ADDR_BITS GET_LOG_OF_2(DCACHE_BYTES_PER_LINE) +#define DCACHE_SET (DCACHE_SIZE/(DCACHE_BYTES_PER_LINE*DCACHE_LINES_PER_SET)) +#define DCACHE_SET_ADDR_BITS GET_LOG_OF_2(DCACHE_SET) + +// Cache行的结构,包括Valid、Age、Dirty、Tag和Data。你所有的状态信息,只能记录在Cache行中! +struct DCACHE_LineStruct +{ + UINT8 Valid; + UINT8 Age; + UINT8 Dirty; + UINT64 Tag; + UINT8 Data[DCACHE_BYTES_PER_LINE]; +}; + +struct DCACHE_SetStruct { + struct DCACHE_LineStruct Line[4]; // 4路组相联Cache,每组有4行 +} DCache[DCACHE_SET]; + + +// DCache初始化代码,模拟器启动时,会调用此InitDataCache函数 +void InitDataCache() +{ + // *********** 你需要在下面书写代码 *********** + for (int i = 0; i < DCACHE_SET; i++) + { + for (int j = 0; j < 4; j++) + { + DCache[i].Line[j].Valid = 0; + DCache[i].Line[j].Age = j; + DCache[i].Line[j].Dirty = j; + DCache[i].Line[j].Tag = 0; + } + } + // *********** 你需要在上面书写代码 *********** +} + +// 在第Set组中,从4路中,找到需要替换的Cache行 +// 如果4行中,有某行的Valid=0,则返回该行行号 +// 否则,返回Age最大的行的行号 +int GetReplaceLine(UINT32 Set) +{ + // *********** 你需要在下面书写代码 *********** + int replace_line = -1; + UINT8 max_age = 0; + + for (int i = 0; i < 4; i++) + { + if (!DCache[Set].Line[i].Valid) + { + return i; + } + if (DCache[Set].Line[i].Age > max_age) + { + max_age = DCache[Set].Line[i].Age; + replace_line = i; + } + } + return replace_line; + // *********** 你需要在上面书写代码 *********** +} + +// 更新Age,在第Set组中,将HitLine指定的Cache行的Age设置为0,其他行的Age要相应调整 +// 注意!要确保4行的Age分别为0~3,且唯一 +void UpdateAge(UINT32 Set, int HitLine) +{ + // *********** 你需要在下面书写代码 *********** + UINT8 old_age = DCache[Set].Line[HitLine].Age; + DCache[Set].Line[HitLine].Age = 0; + + for (int i = 0; i < 4; i++) + { + if (i != HitLine && DCache[Set].Line[i].Valid) + { + if (DCache[Set].Line[i].Age < old_age) + { + DCache[Set].Line[i].Age++; + } + } + } + // *********** 你需要在上面书写代码 *********** +} + +// Data Cache访问接口,Cache模拟器会调用此接口,来实现对你的Data Cache访问 +// Address: 访存字节地址 +// Operation: 操作:读操作('L')、写操作('S')、读-修改-写操作('M') +// DataSize: 数据大小:1字节、2字节、4字节、8字节 +// StoreValue: 当执行写操作的时候,需要写入的数据 +// LoadResult: 当执行读操作的时候,从Cache读出的数据 +// 返回值:'M'表示Miss;'H'表示Hit +UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, + UINT64 StoreValue, UINT64* LoadResult) +{ + UINT32 Set; + UINT8 Block; + UINT64 Tag; + UINT8 MissFlag = 'M'; + UINT64 ReadValue; + int HitLine = 0; + UINT64 HitLineAddress = 0; + + *LoadResult = 0; + + + // Address被划分为 Tag + Set + Block + + // Set Cache的组索引(每组4行) + Set = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) % DCACHE_SET; + Block = Address % DCACHE_BYTES_PER_LINE; + Tag = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) >> DCACHE_SET_ADDR_BITS; // 地址去掉DCACHE_SET、DCACHE_BYTES_PER_LINE,剩下的作为Tag。警告!不能将整个地址作为Tag!! + + // 1. 首先需要检查对应的DCache行,其Valid位是否有效?Tag是否与AddressTag相等? + // 2. 如果Valid有效,且AddressTag相等,则意味着Cache访问“命中” + // 3. 如果“命中”,需要进一步判断是要读('L')还是写('S')或者是修改('M')? + MissFlag = 'M'; + for (int i = 0; i < DCACHE_LINES_PER_SET; i++) + { + if (DCache[Set].Line[i].Valid == 1 && DCache[Set].Line[i].Tag == Tag) + { + MissFlag = 'H'; + HitLine = i; + HitLineAddress = ((DCache[Set].Line[HitLine].Tag << DCACHE_SET_ADDR_BITS) << + DCACHE_BYTES_PER_LINE_ADDR_BITS) | ((UINT64)Set << + DCACHE_BYTES_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址 + break; + } + } + if (MissFlag == 'H') + { + UpdateAge(Set, HitLine); + if (Operation == 'L') // 读操作。从DCache对应行中,读取数据,注意数据宽度与对齐要求 + { + ReadValue = 0; + switch (DataSize) + { + case 1: // 1个字节 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 7]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 6]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 5]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 4]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 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[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 4] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 5] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 6] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 7] = StoreValue & 0xFF; + break; + } + // 写穿透:每次更新Cache行,都需要把数据同时写入Memory + //StoreCacheLineToMemory(HitLineAddress, DCache[Set].Line[HitLine].Data, DCACHE_BYTES_PER_LINE); + DCache[Set].Line[HitLine].Dirty = 1; //标记脏数据 + } + UpdateAge(Set, HitLine); + } + else + { + if (debug) + printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX\n", + __func__, Address, Operation, DataSize, StoreValue); + if (Operation == 'L' || Operation == 'M' || Operation == 'S') // 读操作 + { + // 需要从Memory中读入新的行(真实情况下,这个LoadCacheLineFromMemory需要很长时间的) + int ReLine = GetReplaceLine(Set); + UINT64 ReLineAddress = 0; + + //写回:当被替换的数据为脏数据时,我们需要先将该行数据首先写入到Memory中,再执行替换 + if(DCache[Set].Line[ReLine].Dirty == 1 && DCache[Set].Line[ReLine].Valid == 1) + { + ReLineAddress = (DCache[Set].Line[ReLine].Tag << (DCACHE_SET_ADDR_BITS + DCACHE_BYTES_PER_LINE_ADDR_BITS)) | ((UINT64)Set << DCACHE_BYTES_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址 + StoreCacheLineToMemory(ReLineAddress, DCache[Set].Line[ReLine].Data, DCACHE_BYTES_PER_LINE); + } + + //开始替换,加载新数据 + LoadCacheLineFromMemory(Address, DCache[Set].Line[ReLine].Data, DCACHE_BYTES_PER_LINE); + DCache[Set].Line[ReLine].Valid = 1; + DCache[Set].Line[ReLine].Tag = Tag; + DCache[Set].Line[ReLine].Dirty = 0; + UpdateAge(Set, ReLine); + + if(Operation == 'M' || Operation == 'S') + { + switch (DataSize) + { + case 1: // 1个字节 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 3] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 4] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 5] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 6] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 7] = StoreValue & 0xFF; + break; + } + DCache[Set].Line[ReLine].Dirty = 1; + } + UpdateAge(Set, ReLine); + } + } + return MissFlag; +} + +#ifdef ICACHE_ENABLE + +/* 指令Cache实现部分,可选实现 */ +void InitInstCache(void) +{ + return; +} + +UINT8 AccessInstCache(UINT64 Address, UINT8 Operation, UINT8 InstSize, + UINT64* InstResult) +{ + // ICache只有Operation = 'I'的操作,只会读。不会写 + // 返回值'M' = Miss,'H'=Hit + return 'M'; +} +#endif + +4 +#include +#include "common.h" + +// debug=1:输出额外调试信息 +int debug = 0; + +// 组相联Data Cache +// 容量为16384字节,4路组相联,每行容纳16个字节 +#define DCACHE_SIZE 16384 +#define DCACHE_LINES_PER_SET 4 +#define DCACHE_BYTES_PER_LINE 16 // 必须是8字节的倍数 + +// 下面参数是自动计算的,你无须需改 +#define DCACHE_BYTES_PER_LINE_ADDR_BITS GET_LOG_OF_2(DCACHE_BYTES_PER_LINE) +#define DCACHE_SET (DCACHE_SIZE/(DCACHE_BYTES_PER_LINE*DCACHE_LINES_PER_SET)) +#define DCACHE_SET_ADDR_BITS GET_LOG_OF_2(DCACHE_SET) + +// Cache行的结构,包括Valid、Age、Dirty、Tag和Data。你所有的状态信息,只能记录在Cache行中! +struct DCACHE_LineStruct +{ + UINT8 Valid; + UINT8 Age; + UINT8 Dirty; + UINT64 Tag; + UINT8 Data[DCACHE_BYTES_PER_LINE]; +}; + +struct DCACHE_SetStruct { + struct DCACHE_LineStruct Line[4]; // 4路组相联Cache,每组有4行 +} DCache[DCACHE_SET]; + + +// DCache初始化代码,模拟器启动时,会调用此InitDataCache函数 +void InitDataCache() +{ + // *********** 你需要在下面书写代码 *********** + for (int i = 0; i < DCACHE_SET; i++) + { + for (int j = 0; j < 4; j++) + { + DCache[i].Line[j].Valid = 0; + DCache[i].Line[j].Age = j; + DCache[i].Line[j].Dirty = j; + DCache[i].Line[j].Tag = 0; + } + } + // *********** 你需要在上面书写代码 *********** +} + +// 在第Set组中,从4路中,找到需要替换的Cache行 +// 如果4行中,有某行的Valid=0,则返回该行行号 +// 否则,返回Age=0的行的行号 +int GetReplaceLine(UINT32 Set) +{ + // *********** 你需要在下面书写代码 *********** + int replace_line = -1; + + for (int i = 0; i < 4; i++) + { + if (!DCache[Set].Line[i].Valid) + { + return i; + } + if (DCache[Set].Line[i].Age == 0) + { + replace_line = i; + } + } + return replace_line; + // *********** 你需要在上面书写代码 *********** + return -1; +} + +// 更新Age,在第Set组中,将HitLine指定的Cache行的Age设置为0,其他行的Age要相应调整 +// 注意!要确保4行的Age分别为0~3,且唯一 +void UpdateAge(UINT32 Set, int HitLine) +{ + // *********** 你需要在下面书写代码 *********** + UINT8 old_age = DCache[Set].Line[HitLine].Age; + DCache[Set].Line[HitLine].Age = 0; + + for (int i = 0; i < 4; i++) + { + if (i != HitLine && DCache[Set].Line[i].Valid) + { + if (DCache[Set].Line[i].Age < old_age) + { + DCache[Set].Line[i].Age++; + } + } + } + // *********** 你需要在上面书写代码 *********** +} + +// Data Cache访问接口,Cache模拟器会调用此接口,来实现对你的Data Cache访问 +// Address: 访存字节地址 +// Operation: 操作:读操作('L')、写操作('S')、读-修改-写操作('M') +// DataSize: 数据大小:1字节、2字节、4字节、8字节 +// StoreValue: 当执行写操作的时候,需要写入的数据 +// LoadResult: 当执行读操作的时候,从Cache读出的数据 +// 返回值:'M'表示Miss;'H'表示Hit +UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, + UINT64 StoreValue, UINT64* LoadResult) +{ + UINT32 Set; + UINT8 Block; + UINT64 Tag; + UINT8 MissFlag = 'M'; + UINT64 ReadValue; + int HitLine = 0; + UINT64 HitLineAddress = 0; + + *LoadResult = 0; + + + // Address被划分为 Tag + Set + Block + + // Set Cache的组索引(每组4行) + Set = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) % DCACHE_SET; + Block = Address % DCACHE_BYTES_PER_LINE; + Tag = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) >> DCACHE_SET_ADDR_BITS; // 地址去掉DCACHE_SET、DCACHE_BYTES_PER_LINE,剩下的作为Tag。警告!不能将整个地址作为Tag!! + + // 1. 首先需要检查对应的DCache行,其Valid位是否有效?Tag是否与AddressTag相等? + // 2. 如果Valid有效,且AddressTag相等,则意味着Cache访问“命中” + // 3. 如果“命中”,需要进一步判断是要读('L')还是写('S')或者是修改('M')? + MissFlag = 'M'; + for (int i = 0; i < DCACHE_LINES_PER_SET; i++) + { + if (DCache[Set].Line[i].Valid == 1 && DCache[Set].Line[i].Tag == Tag) + { + MissFlag = 'H'; + HitLine = i; + HitLineAddress = ((DCache[Set].Line[HitLine].Tag << DCACHE_SET_ADDR_BITS) << + DCACHE_BYTES_PER_LINE_ADDR_BITS) | ((UINT64)Set << + DCACHE_BYTES_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址 + break; + } + } + if (MissFlag == 'H') + { + UpdateAge(Set, HitLine); + if (Operation == 'L') // 读操作。从DCache对应行中,读取数据,注意数据宽度与对齐要求 + { + ReadValue = 0; + switch (DataSize) + { + case 1: // 1个字节 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 7]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 6]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 5]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 4]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 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[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 4] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 5] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 6] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 7] = StoreValue & 0xFF; + break; + } + // 写穿透:每次更新Cache行,都需要把数据同时写入Memory + //StoreCacheLineToMemory(HitLineAddress, DCache[Set].Line[HitLine].Data, DCACHE_BYTES_PER_LINE); + DCache[Set].Line[HitLine].Dirty = 1; //标记脏数据 + } + UpdateAge(Set, HitLine); + } + else + { + if (debug) + printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX\n", + __func__, Address, Operation, DataSize, StoreValue); + if (Operation == 'L' || Operation == 'M' || Operation == 'S') // 读操作 + { + // 需要从Memory中读入新的行(真实情况下,这个LoadCacheLineFromMemory需要很长时间的) + int ReLine = GetReplaceLine(Set); + UINT64 ReLineAddress = 0; + + //写回:当被替换的数据为脏数据时,我们需要先将该行数据首先写入到Memory中,再执行替换 + if(DCache[Set].Line[ReLine].Dirty == 1 && DCache[Set].Line[ReLine].Valid == 1) + { + ReLineAddress = (DCache[Set].Line[ReLine].Tag << (DCACHE_SET_ADDR_BITS + DCACHE_BYTES_PER_LINE_ADDR_BITS)) | ((UINT64)Set << DCACHE_BYTES_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址 + StoreCacheLineToMemory(ReLineAddress, DCache[Set].Line[ReLine].Data, DCACHE_BYTES_PER_LINE); + } + + //开始替换,加载新数据 + LoadCacheLineFromMemory(Address, DCache[Set].Line[ReLine].Data, DCACHE_BYTES_PER_LINE); + DCache[Set].Line[ReLine].Valid = 1; + DCache[Set].Line[ReLine].Tag = Tag; + DCache[Set].Line[ReLine].Dirty = 0; + UpdateAge(Set, ReLine); + + if(Operation == 'M' || Operation == 'S') + { + switch (DataSize) + { + case 1: // 1个字节 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 3] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 4] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 5] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 6] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 7] = StoreValue & 0xFF; + break; + } + DCache[Set].Line[ReLine].Dirty = 1; + } + UpdateAge(Set, ReLine); + } + } + return MissFlag; +} + +#ifdef ICACHE_ENABLE + +/* 指令Cache实现部分,可选实现 */ +void InitInstCache(void) +{ + return; +} + +UINT8 AccessInstCache(UINT64 Address, UINT8 Operation, UINT8 InstSize, + UINT64* InstResult) +{ + // ICache只有Operation = 'I'的操作,只会读。不会写 + // 返回值'M' = Miss,'H'=Hit + return 'M'; +} +#endif + +5 +#include +#include "common.h" + +int debug = 0; + +#define DCACHE_SIZE 16384 +#define DCACHE_LINES_PER_SET 4 +#define DCACHE_BYTES_PER_LINE 16 + +#define DCACHE_BYTES_PER_LINE_ADDR_BITS GET_LOG_OF_2(DCACHE_BYTES_PER_LINE) +#define DCACHE_SET (DCACHE_SIZE/(DCACHE_BYTES_PER_LINE*DCACHE_LINES_PER_SET)) +#define DCACHE_SET_ADDR_BITS GET_LOG_OF_2(DCACHE_SET) + +struct DCACHE_LineStruct { + UINT8 Valid; + UINT8 Age; + UINT8 Dirty; + UINT64 Tag; + UINT8 Data[DCACHE_BYTES_PER_LINE]; +}; + +struct DCACHE_SetStruct { + struct DCACHE_LineStruct Line[4]; +} DCache[DCACHE_SET]; + +void InitDataCache() { + for (int s = 0; s < DCACHE_SET; s++) + { + for (int l = 0; l < 4; l++) { + DCache[s].Line[l].Valid = 0; + DCache[s].Line[l].Age = 0; + DCache[s].Line[l].Dirty = 0; + DCache[s].Line[l].Tag = 0; + } + } +} + +int GetReplaceLine(UINT32 Set) { + // 查找无效行 + for (int i = 0; i < 4; i++) { + if (!DCache[Set].Line[i].Valid) return i; + } + + // 查找最大Age行 + int max_age = 0, replace_line = 0; + for (int i = 0; i < 4; i++) { + if (DCache[Set].Line[i].Age > max_age) { + max_age = DCache[Set].Line[i].Age; + replace_line = i; + } + } + return replace_line; +} + +void UpdateAge(UINT32 Set, int HitLine) { + DCache[Set].Line[HitLine].Age = 0; + for (int i = 0; i < 4; i++) { + if (i != HitLine && DCache[Set].Line[i].Valid) { + DCache[Set].Line[i].Age = (DCache[Set].Line[i].Age < 3) ? + DCache[Set].Line[i].Age + 1 : 3; + } + } +} + +UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, + UINT64 StoreValue, UINT64* LoadResult) +{ + UINT32 Set = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) % DCACHE_SET; + UINT8 Block = Address % DCACHE_BYTES_PER_LINE; + UINT64 Tag = Address >> (DCACHE_BYTES_PER_LINE_ADDR_BITS + DCACHE_SET_ADDR_BITS); + UINT8 MissFlag = 'M'; + int HitLine = -1; + + // 检查命中 + for (int i = 0; i < 4; i++) { + if (DCache[Set].Line[i].Valid && DCache[Set].Line[i].Tag == Tag) { + HitLine = i; + MissFlag = 'H'; + break; + } + } + + if (MissFlag == 'H') { + // 处理数据对齐 + switch (DataSize) { + case 2: Block &= 0xFE; break; + case 4: Block &= 0xFC; break; + case 8: Block &= 0xF8; break; + } + + if (Operation == 'L') { + *LoadResult = 0; + for (int i = DataSize-1; i >= 0; i--) { + *LoadResult = (*LoadResult << 8) | DCache[Set].Line[HitLine].Data[Block+i]; + } + } else { + for (int i = 0; i < DataSize; i++) { + DCache[Set].Line[HitLine].Data[Block+i] = (StoreValue >> (i*8)) & 0xFF; + } + DCache[Set].Line[HitLine].Dirty = 1; + } + } else { + if (Operation == 'L' || Operation == 'S' || Operation == 'M') { + int ReplaceLine = GetReplaceLine(Set); + struct DCACHE_LineStruct *line = &DCache[Set].Line[ReplaceLine]; + + // 写回脏数据 + if (line->Valid && line->Dirty) { + UINT64 old_addr = (line->Tag << (DCACHE_SET_ADDR_BITS + DCACHE_BYTES_PER_LINE_ADDR_BITS)) + | (Set << DCACHE_BYTES_PER_LINE_ADDR_BITS); + StoreCacheLineToMemory(old_addr, line->Data, DCACHE_BYTES_PER_LINE); + } + + // 加载新行 + LoadCacheLineFromMemory(Address, line->Data, DCACHE_BYTES_PER_LINE); + line->Valid = 1; + line->Tag = Tag; + line->Dirty = (Operation == 'L') ? 0 : 1; + + // 更新Age状态 + UpdateAge(Set, ReplaceLine); + + // 处理数据对齐 + switch (DataSize) { + case 2: Block &= 0xFE; break; + case 4: Block &= 0xFC; break; + case 8: Block &= 0xF8; break; + } + + // 处理写操作 + if (Operation != 'L') { + for (int i = 0; i < DataSize; i++) { + line->Data[Block+i] = (StoreValue >> (i*8)) & 0xFF; + } + line->Dirty = 1; + } + + // 返回读数据 + if (Operation == 'L') { + *LoadResult = 0; + for (int i = DataSize-1; i >= 0; i--) { + *LoadResult = (*LoadResult << 8) | line->Data[Block+i]; + } + } + } + } + + return MissFlag; +} + +#ifdef ICACHE_ENABLE +void InitInstCache(void) { return; } +UINT8 AccessInstCache(UINT64 a, UINT8 b, UINT8 c, UINT64* d) { return 'M'; } +#endif + +6 +#include +#include "common.h" + +// debug=1:输出额外调试信息 +int debug = 0; + +// 组相联Data Cache +// 容量为16384字节,4路组相联,每行容纳16个字节 +#define DCACHE_SIZE 16384 +#define DCACHE_LINES_PER_SET 4 +#define DCACHE_BYTES_PER_LINE 16 // 必须是8字节的倍数 + +// 下面参数是自动计算的,你无须需改 +#define DCACHE_BYTES_PER_LINE_ADDR_BITS GET_LOG_OF_2(DCACHE_BYTES_PER_LINE) +#define DCACHE_SET (DCACHE_SIZE/(DCACHE_BYTES_PER_LINE*DCACHE_LINES_PER_SET)) +#define DCACHE_SET_ADDR_BITS GET_LOG_OF_2(DCACHE_SET) + +// Cache行的结构,包括Valid、Age、Dirty、Tag和Data。你所有的状态信息,只能记录在Cache行中! +struct DCACHE_LineStruct +{ + UINT8 Valid; + UINT8 NRU; + UINT8 Dirty; + UINT64 Tag; + UINT8 Data[DCACHE_BYTES_PER_LINE]; +}; + +struct DCACHE_SetStruct { + struct DCACHE_LineStruct Line[4]; // 4路组相联Cache,每组有4行 +} DCache[DCACHE_SET]; + + +// DCache初始化代码,模拟器启动时,会调用此InitDataCache函数 +void InitDataCache() +{ + // *********** 你需要在下面书写代码 *********** + for (int i = 0; i < DCACHE_SET; i++) + { + for (int j = 0; j < 4; j++) + { + DCache[i].Line[j].Valid = 0; + DCache[i].Line[j].NRU = 1; + DCache[i].Line[j].Dirty = 0; + DCache[i].Line[j].Tag = 0; + } + } + // *********** 你需要在上面书写代码 *********** +} + +// 在第Set组中,从4路中,找到需要替换的Cache行 +// 如果4行中,有某行的Valid=0,则返回该行行号 +// 否则,返回NRU位为1的行号 +int GetReplaceLine(UINT32 Set) +{ + // *********** 你需要在下面书写代码 *********** + int replace_line = -1; + + for (int i = 0; i < 4; i++) + { + if (!DCache[Set].Line[i].Valid) + { + return i; + } + if (DCache[Set].Line[i].NRU == 1) + { + replace_line = i; + break; + } + } + + //全部为0时,重置为0并返回首行 + if(replace_line == -1) + { + for(int i = 0; i < 4; i++) + { + DCache[Set].Line[i].NRU = 1; + } + replace_line = 0; + } + return replace_line; + // *********** 你需要在上面书写代码 *********** + return -1; +} + + +// Data Cache访问接口,Cache模拟器会调用此接口,来实现对你的Data Cache访问 +// Address: 访存字节地址 +// Operation: 操作:读操作('L')、写操作('S')、读-修改-写操作('M') +// DataSize: 数据大小:1字节、2字节、4字节、8字节 +// StoreValue: 当执行写操作的时候,需要写入的数据 +// LoadResult: 当执行读操作的时候,从Cache读出的数据 +// 返回值:'M'表示Miss;'H'表示Hit +UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, + UINT64 StoreValue, UINT64* LoadResult) +{ + UINT32 Set; + UINT8 Block; + UINT64 Tag; + UINT8 MissFlag = 'M'; + UINT64 ReadValue; + int HitLine = 0; + UINT64 HitLineAddress = 0; + + *LoadResult = 0; + + + // Address被划分为 Tag + Set + Block + + // Set Cache的组索引(每组4行) + Set = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) % DCACHE_SET; + Block = Address % DCACHE_BYTES_PER_LINE; + Tag = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) >> DCACHE_SET_ADDR_BITS; // 地址去掉DCACHE_SET、DCACHE_BYTES_PER_LINE,剩下的作为Tag。警告!不能将整个地址作为Tag!! + + // 1. 首先需要检查对应的DCache行,其Valid位是否有效?Tag是否与AddressTag相等? + // 2. 如果Valid有效,且AddressTag相等,则意味着Cache访问“命中” + // 3. 如果“命中”,需要进一步判断是要读('L')还是写('S')或者是修改('M')? + MissFlag = 'M'; + for (int i = 0; i < DCACHE_LINES_PER_SET; i++) + { + if (DCache[Set].Line[i].Valid == 1 && DCache[Set].Line[i].Tag == Tag) + { + MissFlag = 'H'; + HitLine = i; + HitLineAddress = ((DCache[Set].Line[HitLine].Tag << DCACHE_SET_ADDR_BITS) << + DCACHE_BYTES_PER_LINE_ADDR_BITS) | ((UINT64)Set << + DCACHE_BYTES_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址 + break; + } + } + if (MissFlag == 'H') + { + //UpdateAge(Set, HitLine); + DCache[Set].Line[HitLine].NRU = 0; + if (Operation == 'L') // 读操作。从DCache对应行中,读取数据,注意数据宽度与对齐要求 + { + ReadValue = 0; + switch (DataSize) + { + case 1: // 1个字节 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 0]; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 7]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 6]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 5]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 4]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 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[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 4] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 5] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 6] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 7] = StoreValue & 0xFF; + break; + } + // 写穿透:每次更新Cache行,都需要把数据同时写入Memory + //StoreCacheLineToMemory(HitLineAddress, DCache[Set].Line[HitLine].Data, DCACHE_BYTES_PER_LINE); + DCache[Set].Line[HitLine].Dirty = 1; //标记脏数据 + } + //UpdateAge(Set, HitLine); + } + else + { + if (debug) + printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX\n", + __func__, Address, Operation, DataSize, StoreValue); + if (Operation == 'L' || Operation == 'M' || Operation == 'S') // 读操作 + { + // 需要从Memory中读入新的行(真实情况下,这个LoadCacheLineFromMemory需要很长时间的) + int ReLine = GetReplaceLine(Set); + UINT64 ReLineAddress = 0; + + //写回:当被替换的数据为脏数据时,我们需要先将该行数据首先写入到Memory中,再执行替换 + if(DCache[Set].Line[ReLine].Dirty == 1 && DCache[Set].Line[ReLine].Valid == 1) + { + ReLineAddress = (DCache[Set].Line[ReLine].Tag << (DCACHE_SET_ADDR_BITS + DCACHE_BYTES_PER_LINE_ADDR_BITS)) | ((UINT64)Set << DCACHE_BYTES_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址 + StoreCacheLineToMemory(ReLineAddress, DCache[Set].Line[ReLine].Data, DCACHE_BYTES_PER_LINE); + } + + //开始替换,加载新数据 + LoadCacheLineFromMemory(Address, DCache[Set].Line[ReLine].Data, DCACHE_BYTES_PER_LINE); + DCache[Set].Line[ReLine].Valid = 1; + DCache[Set].Line[ReLine].Tag = Tag; + DCache[Set].Line[ReLine].Dirty = 0; + DCache[Set].Line[ReLine].NRU = 0; + //UpdateAge(Set, ReLine); + + if(Operation == 'M' || Operation == 'S') + { + switch (DataSize) + { + case 1: // 1个字节 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache[Set].Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 3] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 4] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 5] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 6] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache[Set].Line[ReLine].Data[Block + 7] = StoreValue & 0xFF; + break; + } + DCache[Set].Line[ReLine].Dirty = 1; + } + //UpdateAge(Set, ReLine); + } + } + return MissFlag; +} + +#ifdef ICACHE_ENABLE + +/* 指令Cache实现部分,可选实现 */ +void InitInstCache(void) +{ + return; +} + +UINT8 AccessInstCache(UINT64 Address, UINT8 Operation, UINT8 InstSize, + UINT64* InstResult) +{ + // ICache只有Operation = 'I'的操作,只会读。不会写 + // 返回值'M' = Miss,'H'=Hit + return 'M'; +} +#endif + + +7 +#include +#include "common.h" + +int debug = 0; + +#define DCACHE_SIZE 16384 +#define DCACHE_LINES_PER_SET 4 +#define DCACHE_BYTES_PER_LINE 16 + +#define DCACHE_BYTES_PER_LINE_ADDR_BITS GET_LOG_OF_2(DCACHE_BYTES_PER_LINE) +#define DCACHE_SET (DCACHE_SIZE/(DCACHE_BYTES_PER_LINE*DCACHE_LINES_PER_SET)) +#define DCACHE_SET_ADDR_BITS GET_LOG_OF_2(DCACHE_SET) + +struct DCACHE_LineStruct { + UINT8 Valid; + UINT8 NRU; // 3位RRPV实现 + UINT8 Dirty; + UINT64 Tag; + UINT8 Data[DCACHE_BYTES_PER_LINE]; +}; + +struct DCACHE_SetStruct { + struct DCACHE_LineStruct Line[4]; +} DCache[DCACHE_SET]; + +void InitDataCache() +{ + for (int s = 0; s < DCACHE_SET; s++) + { + for (int l = 0; l < 4; l++) + { + DCache[s].Line[l].Valid = 0; + DCache[s].Line[l].NRU = 6; // 初始化为最大值 + DCache[s].Line[l].Dirty = 0; + DCache[s].Line[l].Tag = 0; + } + } +} + +int GetReplaceLine(UINT32 Set) +{ + // 优先替换无效行 + for (int i = 0; i < 4; i++) { + if (!DCache[Set].Line[i].Valid) return i; + } + + // 查找NRU=7的行 + for (int i = 0; i < 4; i++) { + if (DCache[Set].Line[i].NRU == 7) return i; + } + + while(1) + { + // 所有行NRU加1(最大保持7) + for (int i = 0; i < 4; i++) { + DCache[Set].Line[i].NRU = (DCache[Set].Line[i].NRU < 7) ? + DCache[Set].Line[i].NRU + 1 : 7; + } + + // 再次查找 + for (int i = 0; i < 4; i++) { + if (DCache[Set].Line[i].NRU == 7) return i; + } + } + + return 0; // 保底返回首行 +} + +UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, + UINT64 StoreValue, UINT64* LoadResult) +{ + UINT32 Set = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) % DCACHE_SET; + UINT8 Block = Address % DCACHE_BYTES_PER_LINE; + UINT64 Tag = Address >> (DCACHE_BYTES_PER_LINE_ADDR_BITS + DCACHE_SET_ADDR_BITS); + UINT8 MissFlag = 'M'; + int HitLine = -1; + + // 命中检测 + for (int i = 0; i < 4; i++) { + if (DCache[Set].Line[i].Valid && DCache[Set].Line[i].Tag == Tag) { + HitLine = i; + MissFlag = 'H'; + DCache[Set].Line[i].NRU = 0; // 命中行NRU置0 + break; + } + } + + if (MissFlag == 'H') { + if (Operation == 'L') { // 读命中 + *LoadResult = 0; + for (int i = DataSize-1; i >= 0; i--) { + *LoadResult = (*LoadResult << 8) | DCache[Set].Line[HitLine].Data[Block+i]; + } + } + else { // 写命中 + for (int i = 0; i < DataSize; i++) { + DCache[Set].Line[HitLine].Data[Block+i] = (StoreValue >> (i*8)) & 0xFF; + } + DCache[Set].Line[HitLine].Dirty = 1; + } + } + else { // 未命中处理 + if (Operation == 'L' || Operation == 'S' || Operation == 'M') { + int ReplaceLine = GetReplaceLine(Set); + struct DCACHE_LineStruct *line = &DCache[Set].Line[ReplaceLine]; + + // 写回脏数据 + if (line->Valid && line->Dirty) { + UINT64 old_addr = (line->Tag << (DCACHE_SET_ADDR_BITS + DCACHE_BYTES_PER_LINE_ADDR_BITS)) + | (Set << DCACHE_BYTES_PER_LINE_ADDR_BITS); + StoreCacheLineToMemory(old_addr, line->Data, DCACHE_BYTES_PER_LINE); + } + + // 加载新行(写分配) + LoadCacheLineFromMemory(Address, line->Data, DCACHE_BYTES_PER_LINE); + line->Valid = 1; + line->Tag = Tag; + line->NRU = 6; // 初始NRU设为6 + line->Dirty = 0; + + // 写操作处理 + if (Operation != 'L') { + for (int i = 0; i < DataSize; i++) { + line->Data[Block+i] = (StoreValue >> (i*8)) & 0xFF; + } + line->Dirty = 1; + } + + // 读操作设置结果 + if (Operation == 'L') { + *LoadResult = 0; + for (int i = DataSize-1; i >= 0; i--) { + *LoadResult = (*LoadResult << 8) | line->Data[Block+i]; + } + } + } + } + return MissFlag; +} + +#ifdef ICACHE_ENABLE +void InitInstCache(void) { return; } +UINT8 AccessInstCache(UINT64 a, UINT8 b, UINT8 c, UINT64* d) { return 'M'; } +#endif + +9 +#include +#include "common.h" + +// debug=1:输出额外调试信息 +int debug = 0; + + +// 全相联Data Cache +// 容量为16384字节,每行容纳16个字节 +#define DCACHE_SIZE 16384 +#define DCACHE_BYTES_PER_LINE 16 // 必须是8字节的倍数 + +// 下面参数是自动计算的,你无须需改 +#define DCACHE_BYTES_PER_LINE_ADDR_BITS GET_LOG_OF_2(DCACHE_BYTES_PER_LINE) +#define DCACHE_LINES (DCACHE_SIZE/DCACHE_BYTES_PER_LINE) + +#define MAX_AGE (DCACHE_LINES-1) // 每1行有一个唯一的Age + +// Cache行的结构,包括Valid、Age、Dirty、Tag和Data。你所有的状态信息,只能记录在Cache行中! +struct DCACHE_LineStruct +{ + UINT8 Valid; + UINT16 Age; + UINT8 Dirty; + UINT64 Tag; + UINT8 Data[DCACHE_BYTES_PER_LINE]; +}; + +struct DCACHE_SetStruct { + struct DCACHE_LineStruct Line[DCACHE_LINES]; +} DCache; + + +// DCache初始化代码,模拟器启动时,会调用此InitDataCache函数 +void InitDataCache() +{ + // *********** 你需要在下面书写代码 *********** + for (int i = 0; i < DCACHE_LINES; i++) + { + DCache.Line[i].Valid = 0; + DCache.Line[i].Age = i; + DCache.Line[i].Tag = 0; + DCache.Line[i].Dirty = 0; + } + // *********** 你需要在上面书写代码 *********** +} + +// 从所有行中,找到需要替换的Cache行 +// 如果有某行的Valid=0,则返回该行行号 +// 否则,返回Age最大的行的行号 +int GetReplaceLine() +{ + // *********** 你需要在下面书写代码 *********** + int replace_line = -1; + UINT8 max_age = 0; + + for (int i = 0; i < DCACHE_LINES; i++) + { + if (!DCache.Line[i].Valid) + { + return i; + } + if (DCache.Line[i].Age > max_age) + { + max_age = DCache.Line[i].Age; + replace_line = i; + } + } + return replace_line; + // *********** 你需要在上面书写代码 *********** + return -1; +} + +// 更新Age,将HitLine指定的Cache行的Age设置为0,其他行的Age要相应调整 +// 注意!要确保所有行的Age分别为0~MAX_AGE,且唯一 +void UpdateAge(int HitLine) +{ + // *********** 你需要在下面书写代码 *********** + UINT8 old_age = DCache.Line[HitLine].Age; + DCache.Line[HitLine].Age = 0; + + for (int i = 0; i < DCACHE_LINES; i++) + { + if (i != HitLine && DCache.Line[i].Valid) + { + if (DCache.Line[i].Age < old_age) + { + DCache.Line[i].Age++; + } + } + } + // *********** 你需要在上面书写代码 *********** +} + +// Data Cache访问接口,Cache模拟器会调用此接口,来实现对你的Data Cache访问 +// Address: 访存字节地址 +// Operation: 操作:读操作('L')、写操作('S')、读-修改-写操作('M') +// DataSize: 数据大小:1字节、2字节、4字节、8字节 +// StoreValue: 当执行写操作的时候,需要写入的数据 +// LoadResult: 当执行读操作的时候,从Cache读出的数据 +// 返回值:'M'表示Miss;'H'表示Hit +UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, + UINT64 StoreValue, UINT64* LoadResult) +{ + //UINT32 Set; + UINT8 Block; + UINT64 Tag; + UINT8 MissFlag = 'M'; + UINT64 ReadValue; + int HitLine = 0; + UINT64 HitLineAddress = 0; + + *LoadResult = 0; + + + // Address被划分为 Tag + Set + Block + + // Set Cache的组索引(每组4行) + //Set = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) % DCACHE_SET; + Block = Address % DCACHE_BYTES_PER_LINE; + //Tag = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS) >> DCACHE_SET_ADDR_BITS; // 地址去掉DCACHE_SET、DCACHE_BYTES_PER_LINE,剩下的作为Tag。警告!不能将整个地址作为Tag!! + Tag = (Address >> DCACHE_BYTES_PER_LINE_ADDR_BITS); + + // 1. 首先需要检查对应的DCache行,其Valid位是否有效?Tag是否与AddressTag相等? + // 2. 如果Valid有效,且AddressTag相等,则意味着Cache访问“命中” + // 3. 如果“命中”,需要进一步判断是要读('L')还是写('S')或者是修改('M')? + MissFlag = 'M'; + for (int i = 0; i < DCACHE_LINES; i++) + { + if (DCache.Line[i].Valid == 1 && DCache.Line[i].Tag == Tag) + { + MissFlag = 'H'; + HitLine = i; + // HitLineAddress = ((DCache.Line[HitLine].Tag << DCACHE_SET_ADDR_BITS) << + // DCACHE_BYTES_PER_LINE_ADDR_BITS) | ((UINT64)Set << + // DCACHE_BYTES_PER_LINE_ADDR_BITS); // 从Tag中恢复旧的地址 + break; + } + } + if (MissFlag == 'H') + { + if (Operation == 'L') // 读操作。从DCache对应行中,读取数据,注意数据宽度与对齐要求 + { + ReadValue = 0; + switch (DataSize) + { + case 1: // 1个字节 + ReadValue = DCache.Line[HitLine].Data[Block + 0]; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + ReadValue = DCache.Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 0]; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + ReadValue = DCache.Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 0]; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + ReadValue = DCache.Line[HitLine].Data[Block + 7]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 6]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 5]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 4]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 3]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 2]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 1]; + ReadValue = ReadValue << 8; + ReadValue |= DCache.Line[HitLine].Data[Block + 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.Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache.Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache.Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache.Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 4] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 5] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 6] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[HitLine].Data[Block + 7] = StoreValue & 0xFF; + break; + } + // 写穿透:每次更新Cache行,都需要把数据同时写入Memory + //StoreCacheLineToMemory(HitLineAddress, DCache[Set].Line[HitLine].Data, DCACHE_BYTES_PER_LINE); + DCache.Line[HitLine].Dirty = 1; //标记脏数据 + } + UpdateAge(HitLine); + } + else + { + if (debug) + printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX\n", + __func__, Address, Operation, DataSize, StoreValue); + if (Operation == 'L' || Operation == 'M' || Operation == 'S') // 读操作 + { + // 需要从Memory中读入新的行(真实情况下,这个LoadCacheLineFromMemory需要很长时间的) + int ReLine = GetReplaceLine(); + UINT64 ReLineAddress = 0; + + //写回:当被替换的数据为脏数据时,我们需要先将该行数据首先写入到Memory中,再执行替换 + if(DCache.Line[ReLine].Dirty == 1 && DCache.Line[ReLine].Valid == 1) + { + ReLineAddress = DCache.Line[ReLine].Tag << DCACHE_BYTES_PER_LINE_ADDR_BITS; // 从Tag中恢复旧的地址 + StoreCacheLineToMemory(ReLineAddress, DCache.Line[ReLine].Data, DCACHE_BYTES_PER_LINE); + } + + //开始替换,加载新数据 + LoadCacheLineFromMemory(Address, DCache.Line[ReLine].Data, DCACHE_BYTES_PER_LINE); + DCache.Line[ReLine].Valid = 1; + DCache.Line[ReLine].Tag = Tag; + DCache.Line[ReLine].Dirty = 0; + UpdateAge(ReLine); + + if(Operation == 'M' || Operation == 'S') + { + switch (DataSize) + { + case 1: // 1个字节 + DCache.Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache.Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache.Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache.Line[ReLine].Data[Block + 0] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 1] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 2] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 3] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 4] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 5] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 6] = StoreValue & 0xFF; + StoreValue = StoreValue >> 8; + DCache.Line[ReLine].Data[Block + 7] = StoreValue & 0xFF; + break; + } + DCache.Line[ReLine].Dirty = 1; + } + //UpdateAge(ReLine); + } + } + return MissFlag; +} + +#ifdef ICACHE_ENABLE + +/* 指令Cache实现部分,可选实现 */ +void InitInstCache(void) +{ + return; +} + +UINT8 AccessInstCache(UINT64 Address, UINT8 Operation, UINT8 InstSize, + UINT64* InstResult) +{ + // ICache只有Operation = 'I'的操作,只会读。不会写 + // 返回值'M' = Miss,'H'=Hit + return 'M'; +} +#endif + diff --git a/cachelab/Cache.bak2 b/cachelab/Cache.bak2 new file mode 100644 index 0000000..316bc9d --- /dev/null +++ b/cachelab/Cache.bak2 @@ -0,0 +1,403 @@ +/////////////////////////////////////////////////////////////////////// +//// Copyright 2022 by mars. // +/////////////////////////////////////////////////////////////////////// + +#include +#include + +#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) + +/* + 全相联Data Cache,16KB大小 + 每行存放64个字节 +*/ +#define DCACHE_SIZE 16384 +#define DCACHE_DATA_PER_LINE 128 // 必须是8字节的倍数 +#define DCACHE_DATA_PER_LINE_ADDR_BITS GET_POWER_OF_2(DCACHE_DATA_PER_LINE) // 必须与上面设置一致,即64字节,需要6位地址 +#define DCACHE_LINES (DCACHE_SIZE/DCACHE_DATA_PER_LINE) +#define MAX_AGE (DCACHE_LINES-1) // 每行有一个唯一的Age + +// Cache行的结构,包括Valid、Age、Dirty、Tag和Data。你所有的状态信息,只能记录在Cache行中! +struct DCACHE_LineStruct +{ + UINT8 Valid; + UINT16 Age; + UINT8 Dirty; + UINT64 Tag; + UINT8 Data[DCACHE_DATA_PER_LINE]; +} DCache[DCACHE_LINES]; + +/* + 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_LINES; i++) { + DCache[i].Valid = 0; + DCache[i].Age = i; + DCache[i].Dirty = 0; + } +} + +/* + 从Memory中读入一行数据到Data Cache中 +*/ +void LoadDataCacheLineFromMemory(UINT64 Address, UINT32 CacheLineIndex) +{ + // 一次性从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[CacheLineIndex].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 CacheLineIndex) +{ + // 一次性将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[CacheLineIndex].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 WriteData=%016llX\n", __func__, AlignAddress + 8LL * i, WriteData); + } +} + +// 从所有行中,找到需要替换的Cache行 +// 如果有某行的Valid=0,则返回该行行号 +// 否则,返回Age最大的行的行号 +int GetReplaceLine() +{ + int replace_line = -1; + UINT16 max_age = 0; + + for (int i = 0; i < DCACHE_LINES; i++) + { + if (!DCache[i].Valid) + { + return i; + } + if (DCache[i].Age > max_age) + { + max_age = DCache[i].Age; + replace_line = i; + } + } + return replace_line; +} + +// 更新Age,将HitLine指定的Cache行的Age设置为0,其他行的Age要相应调整 +// 注意!要确保所有行的Age分别为0~MAX_AGE,且唯一 +// void UpdateAge(int HitLine) +// { +// for (int i = 0; i < DCACHE_LINES; i++) { +// if (i != HitLine && DCache[i].Valid && DCache[i].Age < MAX_AGE) { +// DCache[i].Age++; +// } +// } +// DCache[HitLine].Age = 0; +// } + +void UpdateAge(int HitLine) +{ + UINT16 old_age = DCache[HitLine].Age; + DCache[HitLine].Age = 0; + + for (int i = 0; i < DCACHE_LINES; i++) + { + if (i != HitLine && DCache[i].Valid) + { + if (DCache[i].Age < old_age) + { + DCache[i].Age++; + } + } + } +} + +/* + 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) +{ + UINT8 BlockOffset; + UINT64 AddressTag; + UINT8 MissFlag = 'M'; + UINT64 ReadValue; + int HitLine = -1; + + *LoadResult = 0; + + // 全相联中,Address被切分为 AddressTag 和 BlockOffset + BlockOffset = Address % DCACHE_DATA_PER_LINE; + AddressTag = Address >> DCACHE_DATA_PER_LINE_ADDR_BITS; + + // 查找是否命中 + for (int i = 0; i < DCACHE_LINES; i++) { + if (DCache[i].Valid == 1 && DCache[i].Tag == AddressTag) { + MissFlag = 'H'; + HitLine = i; + break; + } + } + + if (MissFlag == 'H') // 命中! + { + if (Operation == 'L') // 读操作 + { + ReadValue = 0; + switch (DataSize) + { + case 1: // 1个字节 + ReadValue = DCache[HitLine].Data[BlockOffset + 0]; + break; + case 2: // 2个字节 + BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界 + ReadValue = DCache[HitLine].Data[BlockOffset + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 0]; + break; + case 4: // 4个字节 + BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界 + ReadValue = DCache[HitLine].Data[BlockOffset + 3]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 2]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 0]; + break; + case 8: // 8个字节 + BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界 + ReadValue = DCache[HitLine].Data[BlockOffset + 7]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 6]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 5]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 4]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 3]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 2]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].Data[BlockOffset + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[HitLine].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[HitLine].Data[BlockOffset + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界 + DCache[HitLine].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界 + DCache[HitLine].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界 + DCache[HitLine].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 3] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 4] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 5] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 6] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[HitLine].Data[BlockOffset + 7] = StoreValue & 0xFF; + break; + } + // 写回策略:数据只写入Cache,并设置脏位 + DCache[HitLine].Dirty = 1; + } + // 更新访问时间 + UpdateAge(HitLine); + } + else + { + if (DEBUG) + printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX\n", __func__, Address, Operation, DataSize, StoreValue); + + // 不命中, 获取要替换的行 + int ReplaceLine = GetReplaceLine(); + + // 如果要替换的行有效且脏,则需要写回到内存 + if (DCache[ReplaceLine].Valid == 1 && DCache[ReplaceLine].Dirty == 1) + { + UINT64 OldAddress = DCache[ReplaceLine].Tag << DCACHE_DATA_PER_LINE_ADDR_BITS; + StoreDataCacheLineToMemory(OldAddress, ReplaceLine); + } + + // 需要从Memory中读入新的行 + LoadDataCacheLineFromMemory(Address, ReplaceLine); + DCache[ReplaceLine].Valid = 1; + DCache[ReplaceLine].Tag = AddressTag; + DCache[ReplaceLine].Dirty = 0; + + if (Operation == 'S' || Operation == 'M') // 写操作(修改操作在此等价于写操作) + { + // 写操作,需要将新的StoreValue更新到CacheLine中 + switch (DataSize) + { + case 1: // 1个字节 + DCache[ReplaceLine].Data[BlockOffset + 0] = StoreValue & 0xFF; + break; + case 2: // 2个字节 + BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界 + DCache[ReplaceLine].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 1] = StoreValue & 0xFF; + break; + case 4: // 4个字节 + BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界 + DCache[ReplaceLine].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 3] = StoreValue & 0xFF; + break; + case 8: // 8个字节 + BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界 + DCache[ReplaceLine].Data[BlockOffset + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 3] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 4] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 5] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 6] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[ReplaceLine].Data[BlockOffset + 7] = StoreValue & 0xFF; + break; + } + DCache[ReplaceLine].Dirty = 1; + } + // 更新访问时间 + UpdateAge(ReplaceLine); + + if (Operation == 'L') // 读操作需要返回读取的值 + { + ReadValue = 0; + switch (DataSize) + { + case 1: // 1个字节 + ReadValue = DCache[ReplaceLine].Data[BlockOffset + 0]; + break; + case 2: // 2个字节 + BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界 + ReadValue = DCache[ReplaceLine].Data[BlockOffset + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 0]; + break; + case 4: // 4个字节 + BlockOffset = BlockOffset & 0xFC; // 需对齐到4字节边界 + ReadValue = DCache[ReplaceLine].Data[BlockOffset + 3]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 2]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 0]; + break; + case 8: // 8个字节 + BlockOffset = BlockOffset & 0xF8; // 需对齐到8字节边界 + ReadValue = DCache[ReplaceLine].Data[BlockOffset + 7]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 6]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 5]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 4]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 3]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 2]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[ReplaceLine].Data[BlockOffset + 0]; + break; + } + *LoadResult = ReadValue; + } + } + return MissFlag; +} + +/* 指令Cache实现部分,可选实现 */ +void InitInstCache(void) +{ + return; +} + +void LoadInstCacheLineFromMemory(UINT64 Address, UINT32 CacheLineAddress) +{ + return; +} + +UINT8 AccessInstCache(UINT64 Address, UINT8 Operation, UINT8 InstSize, UINT64* InstResult) +{ + // 返回值'M' = Miss,'H'=Hit + return 'M'; +} + diff --git a/cachelab/Cache.c b/cachelab/Cache.c index 732bb47..316c5f1 100644 --- a/cachelab/Cache.c +++ b/cachelab/Cache.c @@ -1,14 +1,14 @@ -/////////////////////////////////////////////////////////////////////// -//// Copyright 2022 by mars. // -/////////////////////////////////////////////////////////////////////// - +/// +// Copyright 2022 by mars. // +/// + #include #include - + #include "common.h" - + #define DEBUG 0 - + #define GET_POWER_OF_2(X) (X == 0x00 ? 0 : \ X == 0x01 ? 0 : \ X == 0x02 ? 1 : \ @@ -43,87 +43,150 @@ X == 0x40000000 ? 30 : \ X == 0x80000000 ? 31 : \ X == 0x100000000 ? 32 : 0) - + /* - 直接映射Data Cache,16KB大小 - 每行存放64个字节,共256行 + 组相联映射Data Cache,16KB大小 + 每行存放16个字节,共1024行 */ +#define DCACHE_LINE_PER_SET 256 #define DCACHE_SIZE 16384 #define DCACHE_DATA_PER_LINE 16 // 必须是8字节的倍数 #define DCACHE_DATA_PER_LINE_ADDR_BITS GET_POWER_OF_2(DCACHE_DATA_PER_LINE) // 必须与上面设置一致,即64字节,需要6位地址 -#define DCACHE_SET (DCACHE_SIZE/DCACHE_DATA_PER_LINE) +#define DCACHE_SET (DCACHE_SIZE/DCACHE_DATA_PER_LINE/DCACHE_LINE_PER_SET) #define DCACHE_SET_ADDR_BITS GET_POWER_OF_2(DCACHE_SET) // 必须与上面设置一致,即256行,需要8位地址 - -// Cache行的结构,包括Valid、Tag和Data。你所有的状态信息,只能记录在Cache行中! + +// DCache行的结构,包括Valid、Tag、Age、Dirty和Data。你所有的状态信息,只能记录在Cache行中! struct DCACHE_LineStruct { UINT8 Valid; + UINT8 Age; + UINT8 Dirty; UINT64 Tag; UINT8 Data[DCACHE_DATA_PER_LINE]; +}; + +struct DCACHE_Set +{ + struct DCACHE_LineStruct Line[DCACHE_LINE_PER_SET]; }DCache[DCACHE_SET]; - + /* DCache初始化代码,一般需要把DCache的有效位Valid设置为0 模拟器启动时,会调用此InitDataCache函数 */ void InitDataCache() { - UINT32 i; + UINT32 i, j; printf("[%s] +-----------------------------------+\n", __func__); - printf("[%s] | 威震天的Data Cache初始化ing.... |\n", __func__); + printf("[%s] | derder的Data Cache初始化ing.... |\n", __func__); printf("[%s] +-----------------------------------+\n", __func__); for (i = 0; i < DCACHE_SET; i++) - DCache[i].Valid = 0; + { + for (j = 0; j < DCACHE_LINE_PER_SET; j++) + { + DCache[i].Line[j].Valid = 0; + DCache[i].Line[j].Dirty = 0; + DCache[i].Line[j].Tag = 0; + DCache[i].Line[j].Age = j; + } + } } - + +// 在第Set组中,从DCACHE_LINE_PER_SET路中,找到需要替换的Cache行 +// 如果DCACHE_LINE_PER_SET行中,有某行的Valid=0,则返回该行行号 +// 否则,返回Age最大的行的行号 +UINT8 GetReplaceLineData(UINT32 Set) +{ + int max_index = 0; + int max_age = -1; + for (int i = 0; i < DCACHE_LINE_PER_SET; i++) + { + if (DCache[Set].Line[i].Valid == 0) + return i; + if (DCache[Set].Line[i].Age > max_age) + { + max_index = i; + max_age = DCache[Set].Line[i].Age; + } + } + + return max_index; +} + +// 更新Age,在第Set组中,将HitLine指定的Cache行的Age设置为0,其他行的Age要相应调整 +// 注意!要确保DCACHE_LINE_PER_SET行的Age分别为0~DCACHE_LINE_PER_SET-1,且唯一 +void UpdateAgeData(UINT32 Set, UINT8 HitLine) +{ + int HitAge = DCache[Set].Line[HitLine].Age; + DCache[Set].Line[HitLine].Age = 0; + for (int i = 0; i < DCACHE_LINE_PER_SET; i++) + { + if (i != HitLine && DCache[Set].Line[i].Age < HitAge) + DCache[Set].Line[i].Age++; + } +} + /* 从Memory中读入一行数据到Data Cache中 */ -void LoadDataCacheLineFromMemory(UINT64 Address, UINT32 CacheLineAddress) +void LoadDataCacheLineFromMemory(UINT64 Address, UINT32 set, UINT8 line) { // 一次性从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; - } - + + // 地址对齐到缓存行边界 + const UINT64 AlignAddress = Address & ~(DCACHE_DATA_PER_LINE - 1); + UINT64* const cache_line_ptr = (UINT64*)DCache[set].Line[line].Data; + + if (DEBUG) { + printf("[%s] Loading cache line (set=%u, line=%u) from memory %016llX\n", + __func__, set, line, AlignAddress); + } + + // 分8字节块读取 + for (UINT32 i = 0; i < DCACHE_DATA_PER_LINE / sizeof(UINT64); i++) { + const UINT64 read_addr = AlignAddress + i * sizeof(UINT64); + const UINT64 data = ReadMemory(read_addr); + + cache_line_ptr[i] = data; + + if (DEBUG) { + printf(" [LOAD] Address=%016llX -> Data=%016llX\n", read_addr, data); + } + } + } - + /* 将Data Cache中的一行数据,写入存储器 */ -void StoreDataCacheLineToMemory(UINT64 Address, UINT32 CacheLineAddress) +void StoreDataCacheLineToMemory(UINT64 Address, UINT32 set,UINT8 line) { // 一次性将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); - } + + // 地址对齐到缓存行边界 + const UINT64 AlignAddress = Address & ~(DCACHE_DATA_PER_LINE - 1); + UINT64* const cache_line_ptr = (UINT64*)DCache[set].Line[line].Data; + + if (DEBUG) { + printf("[%s] Storing cache line (set=%u, line=%u) to memory %016llX\n", + __func__, set, line, AlignAddress); + } + + // 分8字节块写入 + for (UINT32 i = 0; i < DCACHE_DATA_PER_LINE / sizeof(UINT64); i++) { + const UINT64 write_addr = AlignAddress + i * sizeof(UINT64); + const UINT64 data = cache_line_ptr[i]; + + WriteMemory(write_addr, data); + + if (DEBUG) { + printf(" [STORE] Address=%016llX <- Data=%016llX\n", write_addr, data); + } + } } - + /* Data Cache访问接口,系统模拟器会调用此接口,来实现对你的Data Cache访问 Address: 访存字节地址 @@ -134,57 +197,64 @@ void StoreDataCacheLineToMemory(UINT64 Address, UINT32 CacheLineAddress) */ UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, UINT64 StoreValue, UINT64* LoadResult) { - UINT32 CacheLineAddress; - UINT8 BlockOffset; - UINT64 AddressTag; + UINT8 Block; + UINT32 Set; + UINT64 Tag; UINT8 MissFlag = 'M'; UINT64 ReadValue; - + UINT8 HitLine; + *LoadResult = 0; - + /* - * 直接映射中,Address被切分为 AddressTag,CacheLineAddress,BlockOffset + * 组相联映射中,Address被切分为 Tag,Set,Block */ - - // CacheLineAddress Cache的行号,在直接映射中,就是组号(每组1行) - CacheLineAddress = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) % DCACHE_SET; - 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。警告!不能将整个地址作为Tag!! - - if (DCache[CacheLineAddress].Valid == 1 && DCache[CacheLineAddress].Tag == AddressTag) + + Set = (Address >> DCACHE_DATA_PER_LINE_ADDR_BITS) % DCACHE_SET; + Block = Address % DCACHE_DATA_PER_LINE; + Tag = Address >> (DCACHE_DATA_PER_LINE_ADDR_BITS + DCACHE_SET_ADDR_BITS); + + // 检查命中 + for (int i = 0; i < DCACHE_LINE_PER_SET; i++) { + if (DCache[Set].Line[i].Valid && DCache[Set].Line[i].Tag == Tag) { + HitLine = i; + MissFlag = 'H'; + break; + } + } + + if(MissFlag=='H') { - MissFlag = 'H'; // 命中! - if (Operation == 'L') // 读操作 { ReadValue = 0; switch (DataSize) { case 1: // 1个字节 - ReadValue = DCache[CacheLineAddress].Data[BlockOffset + 0]; + ReadValue = DCache[Set].Line[HitLine].Data[Block + 0]; break; case 2: // 2个字节 - BlockOffset = BlockOffset & 0xFE; // 需对齐到2字节边界 - ReadValue = DCache[CacheLineAddress].Data[BlockOffset + 1]; ReadValue = ReadValue << 8; - ReadValue |= DCache[CacheLineAddress].Data[BlockOffset + 0]; + Block = Block & 0xFE; // 需对齐到2字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 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]; + Block = Block & 0xFC; // 需对齐到4字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 3]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 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]; + Block = Block & 0xF8; // 需对齐到8字节边界 + ReadValue = DCache[Set].Line[HitLine].Data[Block + 7]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 6]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 5]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 4]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 3]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 2]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[HitLine].Data[Block + 0]; break; } *LoadResult = ReadValue; @@ -198,105 +268,347 @@ UINT8 AccessDataCache(UINT64 Address, UINT8 Operation, UINT8 DataSize, UINT64 St switch (DataSize) { case 1: // 1个字节 - DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF; + DCache[Set].Line[HitLine].Data[Block + 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; + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 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; + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 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; + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache[Set].Line[HitLine].Data[Block + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 3] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 4] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 5] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 6] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[HitLine].Data[Block + 7] = StoreValue & 0xFF; break; } + DCache[Set].Line[HitLine].Dirty = 1; } + UpdateAgeData(Set, HitLine); } - else + else if(MissFlag=='M') { if (DEBUG) printf("[%s] Address=%016llX Operation=%c DataSize=%u StoreValue=%016llX\n", __func__, Address, Operation, DataSize, StoreValue); - MissFlag = 'M'; // 不命中 - if (DCache[CacheLineAddress].Valid == 1) - { - // 淘汰对应的Cache行,如果对应的Cache行有数据,需要写回到Memory中 - 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); - } - // 需要从Memory中读入新的行(真实情况下,这个LoadCacheLineFromMemory需要很长时间的) - LoadDataCacheLineFromMemory(Address, CacheLineAddress); - DCache[CacheLineAddress].Valid = 1; - DCache[CacheLineAddress].Tag = AddressTag; + UINT8 replace_Line = GetReplaceLineData(Set); + if (Operation == 'L') // 读操作 { - // 读操作不需要做事情,因为已经MISS了 + // 写回脏行 + if (DCache[Set].Line[replace_Line].Valid && DCache[Set].Line[replace_Line].Dirty) + { + UINT64 victim_addr = (DCache[Set].Line[replace_Line].Tag <<(DCACHE_DATA_PER_LINE_ADDR_BITS + DCACHE_SET_ADDR_BITS)) |(Set << DCACHE_DATA_PER_LINE_ADDR_BITS); + StoreDataCacheLineToMemory(victim_addr,Set,replace_Line); + } + + // 加载新数据 + LoadDataCacheLineFromMemory(Address,Set,replace_Line); + + DCache[Set].Line[replace_Line].Valid = 1; + DCache[Set].Line[replace_Line].Tag = Tag; + DCache[Set].Line[replace_Line].Dirty = 0; + + ReadValue = 0; + switch (DataSize) + { + case 1: // 1个字节 + ReadValue = DCache[Set].Line[replace_Line].Data[Block + 0]; + break; + case 2: // 2个字节 + Block = Block & 0xFE; // 需对齐到2字节边界 + ReadValue = DCache[Set].Line[replace_Line].Data[Block + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 0]; + break; + case 4: // 4个字节 + Block = Block & 0xFC; // 需对齐到4字节边界 + ReadValue = DCache[Set].Line[replace_Line].Data[Block + 3]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 2]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 0]; + break; + case 8: // 8个字节 + Block = Block & 0xF8; // 需对齐到8字节边界 + ReadValue = DCache[Set].Line[replace_Line].Data[Block + 7]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 6]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 5]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 4]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 3]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 2]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 1]; ReadValue = ReadValue << 8; + ReadValue |= DCache[Set].Line[replace_Line].Data[Block + 0]; + break; + } + *LoadResult = ReadValue; } else if (Operation == 'S' || Operation == 'M') // 写操作(修改操作在此等价于写操作) { + // 写回脏行 + if (DCache[Set].Line[replace_Line].Valid && DCache[Set].Line[replace_Line].Dirty) + { + UINT64 victim_addr = (DCache[Set].Line[replace_Line].Tag << (DCACHE_DATA_PER_LINE_ADDR_BITS + DCACHE_SET_ADDR_BITS)) | (Set << DCACHE_DATA_PER_LINE_ADDR_BITS); + StoreDataCacheLineToMemory(victim_addr,Set,replace_Line); + } + + // 加载新数据 + LoadDataCacheLineFromMemory(Address, Set, replace_Line); + + DCache[Set].Line[replace_Line].Valid = 1; + DCache[Set].Line[replace_Line].Tag = Tag; + DCache[Set].Line[replace_Line].Dirty = 0; + // 写操作,需要将新的StoreValue更新到CacheLine中 switch (DataSize) { case 1: // 1个字节 - DCache[CacheLineAddress].Data[BlockOffset + 0] = StoreValue & 0xFF; + DCache[Set].Line[replace_Line].Data[Block + 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; + Block = Block & 0xFE; // 需对齐到2字节边界 + DCache[Set].Line[replace_Line].Data[Block + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 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; + Block = Block & 0xFC; // 需对齐到4字节边界 + DCache[Set].Line[replace_Line].Data[Block + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 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; + Block = Block & 0xF8; // 需对齐到8字节边界 + DCache[Set].Line[replace_Line].Data[Block + 0] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 1] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 2] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 3] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 4] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 5] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 6] = StoreValue & 0xFF; StoreValue = StoreValue >> 8; + DCache[Set].Line[replace_Line].Data[Block + 7] = StoreValue & 0xFF; break; } + DCache[Set].Line[replace_Line].Dirty = 1; + UpdateAgeData(Set, replace_Line); } + UpdateAgeData(Set, replace_Line); } return MissFlag; } - -/* 指令Cache实现部分,可选实现 */ + +/* + 组相联映射Instruction Cache,16KB大小 + 每行存放16个字节,共1024行 +*/ +#define ICACHE_LINE_PER_SET 64 +#define ICACHE_SIZE 16384 +#define ICACHE_DATA_PER_LINE 16 // 必须是8字节的倍数 +#define ICACHE_DATA_PER_LINE_ADDR_BITS GET_POWER_OF_2(ICACHE_DATA_PER_LINE) +#define ICACHE_SET (ICACHE_SIZE/ICACHE_DATA_PER_LINE/ICACHE_LINE_PER_SET) +#define ICACHE_SET_ADDR_BITS GET_POWER_OF_2(ICACHE_SET) + +// ICache行的结构 +struct ICACHE_LineStruct +{ + UINT8 Valid; + UINT8 Age; + UINT64 Tag; + UINT8 Data[ICACHE_DATA_PER_LINE]; +}; + +struct ICACHE_Set +{ + struct ICACHE_LineStruct Line[ICACHE_LINE_PER_SET]; +}ICache[ICACHE_SET]; + + void InitInstCache(void) { - return; + UINT32 i, j; + printf("[%s] +-----------------------------------+\n", __func__); + printf("[%s] | derder的Inst 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].Line[j].Valid = 0; + ICache[i].Line[j].Tag = 0; + ICache[i].Line[j].Age = j; + } + } } - -void LoadInstCacheLineFromMemory(UINT64 Address, UINT32 CacheLineAddress) + +//LRU替换策略 +UINT8 GetReplaceLineInst(UINT32 Set) { - return; + int max_index = 0; + int max_age = -1; + for (int i = 0; i < ICACHE_LINE_PER_SET; i++) + { + if (ICache[Set].Line[i].Valid == 0) + return i; + if (ICache[Set].Line[i].Age > max_age) + { + max_index = i; + max_age = ICache[Set].Line[i].Age; + } + } + + return max_index; } - + +// 更新Age,在第Set组中,将HitLine指定的Cache行的Age设置为0,其他行的Age要相应调整 +// 注意!要确保ICACHE_LINE_PER_SET行的Age分别为0~ICACHE_LINE_PER_SET-1,且唯一 +void UpdateAgeInst(UINT32 Set, UINT8 HitLine) +{ + int HitAge = ICache[Set].Line[HitLine].Age; + ICache[Set].Line[HitLine].Age = 0; + for (int i = 0; i < ICACHE_LINE_PER_SET; i++) + { + if (i != HitLine && ICache[Set].Line[i].Age < HitAge) + ICache[Set].Line[i].Age++; + } +} + +void LoadInstCacheLineFromMemory(UINT64 Address, UINT32 set, UINT8 line) +{ + // 一次性从Memory中将ICACHE_DATA_PER_LINE数据读入某个Instruction Cache行 + // 提供了一个函数,一次可以读入8个字节 + + // 地址对齐到缓存行边界 + const UINT64 AlignAddress = Address & ~(ICACHE_DATA_PER_LINE - 1); + UINT64* const cache_line_ptr = (UINT64*)ICache[set].Line[line].Data; + + if (DEBUG) { + printf("[%s] Loading cache line (set=%u, line=%u) from memory %016llX\n", + __func__, set, line, AlignAddress); + } + + // 分8字节块读取 + for (UINT32 i = 0; i < ICACHE_DATA_PER_LINE / sizeof(UINT64); i++) { + const UINT64 read_addr = AlignAddress + i * sizeof(UINT64); + const UINT64 data = ReadMemory(read_addr); + + cache_line_ptr[i] = data; + + if (DEBUG) { + printf(" [LOAD] Address=%016llX -> Data=%016llX\n", read_addr, data); + } + } + +} + UINT8 AccessInstCache(UINT64 Address, UINT8 Operation, UINT8 InstSize, UINT64* InstResult) { - // 返回值'M' = Miss,'H'=Hit - return 'M'; -} \ No newline at end of file + UINT32 set = (Address >> ICACHE_DATA_PER_LINE_ADDR_BITS) % ICACHE_SET; + UINT8 block = Address % ICACHE_DATA_PER_LINE; + UINT64 tag = Address >> (ICACHE_DATA_PER_LINE_ADDR_BITS + ICACHE_SET_ADDR_BITS); + UINT8 HitLine; + UINT8 MissFlag = 'M'; + UINT64 ReadValue = 0; + + *InstResult = 0; + + // 检查命中 + for (int i = 0; i < ICACHE_LINE_PER_SET; i++) { + if (ICache[set].Line[i].Valid && ICache[set].Line[i].Tag == tag) { + HitLine = i; + MissFlag = 'H'; + break; + } + } + + if (MissFlag == 'H') { + // 命中处理 + switch (InstSize) { + case 1: // 8位指令 + block = block & 0xFF; // 对齐到1字节边界 + ReadValue = ICache[set].Line[HitLine].Data[block + 0]; + break; + case 2: // 16位指令 + block = block & 0xFE; // 对齐到2字节边界 + ReadValue = ICache[set].Line[HitLine].Data[block + 1]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 0]; + break; + case 4: // 32位指令 + block = block & 0xFC; // 对齐到4字节边界 + ReadValue = ICache[set].Line[HitLine].Data[block + 3]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 2]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 1]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 0]; + break; + case 8: // 64位指令(如RISC-V的128位指令集扩展) + block = block & 0xF8; // 对齐到8字节边界 + ReadValue = ICache[set].Line[HitLine].Data[block + 7]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 6]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 5]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 4]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 3]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 2]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 1]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[HitLine].Data[block + 0]; + break; + default: + // 不支持的指令长度 + return 'M'; + } + *InstResult = ReadValue; + UpdateAgeInst(set, HitLine); + } + else + { + // 未命中处理 + UINT8 replace_line = GetReplaceLineInst(set); + LoadInstCacheLineFromMemory(Address, set, replace_line); + + // 重新读取指令 + switch (InstSize) { + case 1: + block = block & 0xFF; + ReadValue = ICache[set].Line[replace_line].Data[block + 0]; + break; + case 2: + block = block & 0xFE; + ReadValue = ICache[set].Line[replace_line].Data[block + 1]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 0]; + break; + case 4: + block = block & 0xFC; + ReadValue = ICache[set].Line[replace_line].Data[block + 3]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 2]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 1]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 0]; + break; + case 8: + block = block & 0xF8; + ReadValue = ICache[set].Line[replace_line].Data[block + 7]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 6]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 5]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 4]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 3]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 2]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 1]; ReadValue = ReadValue << 8; + ReadValue |= ICache[set].Line[replace_line].Data[block + 0]; + break; + default: + // 不支持的指令长度 + return 'M'; + } + *InstResult = ReadValue; + ICache[set].Line[replace_line].Valid = 1; + ICache[set].Line[replace_line].Tag = tag; + UpdateAgeInst(set, replace_line); + } + return MissFlag; +} diff --git a/cachelab/Makefile b/cachelab/Makefile index 799f83a..6228e56 100644 --- a/cachelab/Makefile +++ b/cachelab/Makefile @@ -8,7 +8,7 @@ LDFLAGS += LDLIBS += -lzstd -CPPFLAGS := -O3 -Wall -Wextra -Winline -Winit-self -Wno-sequence-point\ +CPPFLAGS := -Ofast -Wall -Wextra -Winline -Winit-self -Wno-sequence-point\ -Wno-unused-function -Wno-inline -fPIC -W -Wcast-qual -Wpointer-arith -Icbsl/include #CPPFLAGS := -g @@ -19,7 +19,7 @@ objects = Cache.o CacheHelper.o getopt.o cbsl/src/buffer.o cbsl/src/file.o cbsl/ all: $(PROGRAMS) Cache : $(objects) - gcc $(CPPFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) + icx $(CPPFLAGS) $(LDFLAGS) -o $@ $^ $(LDLIBS) rm -f $(objects) clean: diff --git a/perflab/matrix/rowcol.c b/perflab/matrix/rowcol.c new file mode 100644 index 0000000..e69de29 diff --git a/perflab/poly/a.exe b/perflab/poly/a.exe index 305d39be7b9a2babbb63cd84f6e8854f2a118ea8..9e6e9280f558c518382d4b6f86d1d0a58e630c53 100755 GIT binary patch literal 164791 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

w*p2atg*u6d6x< z*71d4quu%f$|v$Xo8_vXYklRZR2H`38rUsd^>eMSSN$)qVrjgD3p`xwa};Pzb-N9i zqhEuE9zoE7hu>%gw{S(zbv)P2t*IBIMRL1^t97o?^=f^4RegxDQXP!3MXuJlM%SzL z1E8S$u_EKwwcyb>j6ICa@QL5&*6RFGZB8qGpFth=Sntxr1mUary=}GR1bW0T^w+U< z;;$AcjKxNx&|hD|8SO9bbNhw97yw!OB7<+f8R?4w_+9&g7uWf+xVyqVl{TDhO;yhc z^w&+0r~P%KF9>z%ubDdj5?^o!T>B#SbU4&!S&lDe!LNM*!HFWi$XH(_7|bDk@p}x} z^i*r=UHH$e=N9^6FVcYaMTdLR9MKok_}<7DpS8Z=8ybAQY3DZZXgmMGwC;Y;&U43M z=eJoqM-QWumO?!1lY8_U^O*qG<9fyS1v;515*KrzYhNT7aCkhvkbGD~BaGnIYB|&F zrtSa_z0{ggUh@Rfr*FSNeEKHz%*_d`K%d@aeJXFuaGRn}XF+O|>z1rf8DG@QE%fQr zp@R15G54f7qE8p<_>^qSbw|+~o$LJEEAjO~>-X5B_>3I!>HONpxo&wrg>qF2eX#;V ziH@&#g2T5Deeomfi`wzEfZC1Xi*r_;>uSeWD`Yi_ucbP^5IcX@+8NKV(2pu~EN~&@ zXgg)L@v!$k>JmK#kTQ3_-!B;*_PZp)s^G{X&@| z`F{TjcwApBCzd-`%NPfti6XIFva#%sDKfX|x3LT(B<409c-Gn4{vBHfq+iwMTAF7| ze?1eQ>n3aYnmjwMA93`l*3OPief4+%skPT% ze?8YUT-jHhZ-7TyuEoRijKN!M41OKBSTEJ=Cu8s#5T?gC{sQb5&JX_$XY~B=x%lR@ zF=sBJYNMPvZR7L|G(v8-a89xT*no-9cPu%p6z0_@(HkAhSNO`H4)baoIt8`R4mI}+ z^D5s3r1R<_FhIFjk$C?(Bc zt5WFG>t5P8uRaD~G@7?$plhSN`q!{?qr7?X}d)pqW) zcJ_^|<9*BZo%P~W<&7)|v!|0&zR=&I29dV_2;Wm405jJ=9cA*adoj_ z#*}$GrqG%&R;eSmaJb_js}LIl78{+QFk88W>&jCBJ&lbo;-rs5=I1Rizs82V$H!z_F}LiNj3BcL3G5fCZfVY^zQ5Qi zGxkfih%asd#kUWA@fquj4p$j-L|@EXUt~b_6=zgtFbX%y*sq42wJ%Qf8$ruzhoO!y zB;uy6o#Rig>&O|EgZ)T|+RmA(b|&9Dz|;0(9hK-J${nB#t~T?Z01s}(W<{TQeWJ)d zKU1mJhqr3FMaGmn(E*($n4jGuS>jgk=q#}o3@}8Z5Sd@tBFKCNegVBJ!J+p_erl0< zlxlf}W6B-)(_;#EnPJdGA$k|ln|hgqSBvq^U46CKD-$i~Ln8coi|}d4;;->;;o9L^ z^qm%NP5J#MH#5=@!bC^GoM&h9s6ClFQ9vG!O?*$L&Pa=(xS3WWg;`yXP3}{E*#=7o ze7^XS+G7)Wwt}bRAp9y&GV3G?1gp9o_I(FxLAh924pyzKYU^AxT0LNK$eD>-1c!$$ z4!IBD+J`lW!xgU*9R3E}zE7DU{~kt$JJ7GwtNM#LTn%??9G2O%SBS$lNT`QHV(k0C zhR&j&L>ovXi9-CG0f1@zyril=#Lv=k;D>nC`00Zb9K?#?=RW9c)}GNboeiJ(y;C89 z_K7thJj+;nP7B7W?7^}*`s?3bBmO!73bTw`=&x_VKH6U$?n!e*U-VdCWI**5r!Nk` zINBGr_w+Ww5!x5E_srYS2t*P;MuX@>?EIj$Gp^>0JwRGG3y3&v=lW}q$QOL$EYTK~ z<;38O^#uecii`z|&{>ZKRd{6Y3vr?Q-i}t>{48<0fOhD~db7$KUn_q9F}#8c5fX)d z|AY1W8_}X)7^m;Eaq3^=!5YNI0BSdi(=S_WybE?F+T6m}3egT!LcjRmFO21j0SS$t zvJXO3VJvsn!4F&ISipA;N3ra7UoiFr$U2s>foC3>9oK3aZd^J?XZ32aOx{`W%nLSD zrn0oWdNa<3V>C`^AIS99G+i+vf%KX2G0zfE43uIXnknIU1(~JYEAwv2hUbR zwFfIL=ug4(BaqN&Zu0~1XsmXkvOo4PM-7b$R{tvizsVJbBVu(QCL20OWznKvh}Hjv z+Imd+r>Z=}>I}}*!z$-C_o8_{x4~4aN@1?M0w4s;VCN~cNF=$1Sp6g#(OBg#e10KT z`_KqPq8&fiu?Dev7=Ie8?Pv!;h!w$V8yc}#HBamoHo?XKTtG#tVb#Y5geHpoK1f(` zq^;HttI($x?h~KB1C;9H&i8S>U!!qn-uf$pc6>9^7hi{+wJ&OqJ6({aeZjpZzt9(@ zI=&D)ueEk&8(hOhWjS`9M>TY^Ovoi7r@}p*%&)3&Ja5@2K~agSUe6p zr!d`XWao8|rR~h$3;aSm&m4!HFSmBK&k}bU>tgm(c&=g7f2)Sx|Z$%Dte>JCv`m)&=oei=fO{ijR%Q(L+{>ip1bNNR6<;Yw&8t zpycOOkf8H3CY*^PbBWWyqw{kS9M4N|=~%u6zgaSKx2C=e1pJf7LyT>-7<*?`urfb? z0m05J*P1$pBfk)bOool}^EbeTkViYdE^L)K;V&#!d3_yQbPMMj>K@lv-SJg zfNXWYKWP2_p;+zcD{VMnZJ5CcAJW9ePb@ZQpXzJs-&%h?+iwK5nCBPj#D&=TW@~4? z11wMDM-$Nw@UrX-!HFW{Y3G{SIg?dr=a1hZ@pU^WZd{Oux$Xwor_sD+`Z(rQ@*k$E}^!)6(s03D_q&(0Hysp8A;Qn$lHCFu#B^jH={bE=cwo zvwFUe@oOt|*5j8u=T+^PYs?s@^isXGaPO@6^uyqWu@Z$oy~Fx+quVLN%kk?oaD*Pe z`V%eaL&n!E{_2fijITlK_cAKFa{#u)e7F)rvG#lIYi`?+#2^lRcgGX9!NOeEXLH>~ zP^#y;BWOgAI|>{2bp&if9|Xa?ioyEW&^*+qN?tYlT8l=D|5I%2fX*5l{+d$eHoU*R z5jGzFh+yMha5JjiLTvmq{H5c)SQ8u1fD1G>&T$(sN5sYg{u*K9L5q#|qh@t%yyh&y z#+Iu3F!%CKNrOtUQ|Jo|^TtIM8<%Ke;y#QfLD?vyupQs~6n-d6YDS-e4MIj^gERI-9y8ZsSy?sq4>`VHbdU8Z-0zPojQ6`> zL+#T;ks`5g^cU~q*Z#`jjITKJ{BNwkUIiXZpO%1b~KM3 zM!guC7@6dK{IQ;zUQA0bs(8z@YBipuovZO^JD-09Iw>2bRx11BpDo50zUAZIF@1kS zoxTe;PGG;fxO1ZW#s6**j7^D`t6fE4kak0{O=a&MW=YV z4(3M$8>!W9BRKNMo>zZ-{!V$gC}(6TJ)Ydz_Up7evB z^uwO?`KF||d(t2Aq|bWNd9}RT_ajY7U*}0*@}wW~q(A6MUua5trzicGCw`CXfO|In^o07iKlfJ@}KJQ6i=}CX2Dd`t`(%U@gM?C3gN77BM zqt1<1Qc>heo{vShiQFaW#Ry+}i5;_h(Cfu}AiaiTH#55rzbtYwliV6%$*ZYdcP;V&69r6lfI%U>0kAv4|vj7dD8Fpq_;IC{fH-> z*SNZt&v?@B^Q5n8O8PfF=_OBkyC?mNp7cyp()mEUhp7c&n`cY4MdsEUM@T5h2mptj6O-VoMNk8C8@A0Ic409QanbBcmQ__FnNuTkg_j=M-deVEElD_CkKj=yC z^Q52UN$+h+`p-P+v!3*RPx{L|>3vN}f5el1$df+cNq?;;y}v2xzw)HddD2Hb>96yo z4>Tp6PrpY~939LUci59&@}%GFNgruS`eUB-c~AP3C;e7WdZ{Vt$2{prJn7S(^xGoo zW@h!FIx{QTJqT<&GOO&3WSP7%ExkAav&t#>T=~7utTNJBuo2VQ^!;;n`YyAt6WFh= z17hdeGsf-(v3zZw4NnL*Qfu5PC62s)Eo)wPv$Sj@u2b|8O4lPmfbxz|*7tj3+yOz$ zvZT*=(l>h2mptjENV@U+*XsCPFdy|oFdyrw!TgN$;sjuR@Hj9Z=`5I!>1_JWr&QNk zMG7`fV86PKiN9*&xw}WaT$^XZt%8j@n792}7V1W%Wx;cIM)VO%7oYRyVt%WKx<~3* zUQidcBdCkXGN_w9fvDs41&uOoq_dzdrn5oa>9|~>5$aB0zq%I1U$qg{-7a3P&9k8> z*r@m0g!pS&sM{zl3&y&S$?qvb>6(*X2xa}M2z3T6hnkXpu_rwcYly6_!=ChZPx@R_ z(tAAVD?I7*p7irQ>4%$=zSWc7=1D)|NxvwPZZMyzgL#Sf$b%B^F)ter&Py*KGg+SM zD<3XCj(CrBmUxfpZ2Eq2oxaN`a{~L-wIKeg9n0OT#LKmLHt^Y5Iv@Jaxbyn8tax7` zElYfLFM9EQPyN?CX52AoIU;CTmh{s+>5q8QXFch>gT_cVe*eoleizI~y%5aD zdTM&HAiX#Nm~TH0%tty4=3_dWzK3=CF4#DM{pva-{;G}V?j`YZZJrHnf{oORlIh2; zCTzc!g}TM)Hx9FYUl4r+;avx$7eZOT6k*<2ezcC|1$9w7g1VS2gSy2Nh&oGPiSV@*k) z_oN^4qz`z~4|~#s3!C{>=zdT7tS7zSlm0eaMr(swwGwKDI%x$@2$1={=tG9iH?|Q_?@-NuTzlZ}g;d2J1##dsEU6dD5pm z>7AbRwVw3OrljBLNiTWQ+db(kJn0*ol0NH6AMvDTJn4^PpTxC%PgBxw^P~@W(pP!X zf8t5+ZA$tZ=apS$$lOA}|=RD~H zO-aAmliuS=KPIy;JH8+Eq>nTuozD$%`@YeWzT`=tj-;D;-6>dG)!H$WIc?OA%xPn? z%q*oOy@0)w<*D91VEd}?b$*wLbe1`7OlQ;g=hW%D%xO9=e7vUtzhG<1ebz|)EU_yI3bcz5jk zQB^;w=D9fQx5K+c7jO@qzOO9@9$LXIyl3r7{G6xn5`Ek~X^wc$+MKnIdoP+);5};` zoAfl07 zBkwrSc<0t&$yVXuj3(v$)6z#>ttQBWRn zC~ssfhw>g$9O<{Q9TarUw0sXJbB?TUfTFK~$rzqTLE-#R_X3tL@;yHC78tGJZ@?q- zLrrNxQkix1c_ApScP?b&LCXj+v>xWv?bJb=T2qVkZQvOI&maG&H3)*&uomnbQTBi` z3(H3KxsyC7x@CU*yrN`cwH^S4IE~ujQ*YgVwICJAxC{;1_d)36c=>Gb%-NoXcsOQ= z{6JkIJ8uCG5v6?^;>K*=@;T!m^E#$IC=~S~4~p#VXseb%>9?}LAG`$=dHaUuc|Ryz z%kW53?gqu}*JnYId(t(}1E6FaB>V~#mZ%rM1!aZB>S%s6SK5bXRat-S$(MJHSMtGQ zs1;fM8Hh+;->dsxnw-Gs1@t<7KbvXTk-ilad5UaHFq$nE$8(5ND=Xy75-!`R-g6!G z^fjm@J3_i&1-6V5>9ZFUxgJ3C+ycsoWBCuW9sETxIFF-Vhv(a%xH$X;YdPLI2}tQj zBau&6fzoEJLCw~Ia?q)DB`6zL$93Kfiv0Gcb>0Jt>;q`ZTR@p|+PR$+2MM1BMSiQ* zwH85{w^pS-zXpZBJVt##8G~)7qtEj}k$peiP6sIMsI7Zi&W;V97Z!tGff89h3!Z7T z9<_V}DE*FA-v~;{;h6zt%CX@+Y)9!V2%iUqSH8xlJY1h3yuvR1EmjVB4lPTHJfqp zvyPKB_#*1pW>9VgG1BmQPzD@4m&gNsbjvvtxrZE`{{}qMm@7m`xCfNT&JYoNfjka2 z{t=Y>a3<3EH=t~EbZ%LhjNA+;Z8mFd>ANPpY-GRka^&}mz{8vX*Big@0!5b$-ZHrg zUwJMnR~Pg75)NetSbJwbYV}!bgt_t2(ec52j}tpLfoI0S*g;TuS4-5dyFhXC&%K}= zfxL+4C!id2eEK*j^2@c>XU%h9A4k@DP*|dN`atQi_TjlKDQhHhG3pgK*OK=@>6S;q zGXq{8Y08bDBt{}oN)9qV3ySQ(=vv5+I|qzPYEW`Uhoj(`b=r9xlpd$=&prbb z)Qo)kXP{hcEng`NkMSMjB?pJ?;F)t|@yVU5Y%S_?1t=LrfyQcOqCA!xEd}~sSL)1? z1&{3MY04f@To2v|N}JP*yFfYOp!d_D%%Z)>JBy&etue~8o{NZc+UX?4vBssK9B?Rk zP!^q>un!cuLQ?Deakk^=b3Z5<2buo}N}I#;ASk@6PTL2n2EQT2LEWhcAh}ap%X%I| z3SXn#(gDgMh>-^`1f}Hk;yO@{Y-B8o<%dD(aVT#CrQMf^svY zN0$F3C<|IieeVF}2rL%K3PFkHnP3b? z*)oKoqN()(Q2H(u3Ppv3g*}hpd}Ogh;2E*~Qu9?ruDsQeGgwSb&Gq$w+~QIv7o`7==3tqs8(bbzwx^mG#_2OP@P zpfI;YIv)U~*U|Zdpy<2UGDe@fKsjuAhA=hHLEcg89fco|`W<~fPg&rL)&*Y&MSiE& z{W=Cpr<0?ef#(&;uV$L(g`mt^>Fnu7P!2i$>IJ3C@lF<$S@5zA-OeZ|Twg?ezYUZD zXLfNPC6JM$iM0mX`4o64C(`g+ps+-ghe4TfynGUN z_@`Popx5@ zlDd-ZDQlev$`n2$gb#ocd51i21|>+v`~E&sRLNil`&@j(=|uS$cov*~9R?-iwDTQM z+${QIP>whj`yD7fPOTLg=*2iI} zAIMx7+mkI82K%7JXnrDJ?!_=%%$MRv%xD1W0xgNTGZCt3)Hx_HZK+46zEqFZ{LJ( zI%w-eX(Hkh;cWxZG!zf&c-$h^bsTGO?B9?6Xwf?K!*E+~c*#|pF54Dv-gfEs%P|KY z%$M`S2=;tgCzuc~-U-9uTrPC!hL~iJWu|N{m#+XE_m-_AQ9 z9UZGogjLRkrF?lPL_a2i@+1-|{uFOOlK!fy!MIasG=7q;1()W>@@3fXD&+U^(aR@C zcfqlV2q{q=)$~MmmlC5Ci6pDv=sQfR3G{Q^qeJz`c6*4O#qn^sIKB%_4yvDc;}rd) zZ$f?;ZrORoRpE~9SMLOznR^NtoA&3K2PGr1sn}uF6l-?Ukvxk)zNt~pm#~Q%=F4R? zFq$pz=G#nF;^H(oy9@Ax@2=LgN&tHoF4(v}gt0j(MhF2~n8}rcy-1rB3s`#kUvx)0&DJZztG4a9VvFw2w#&DoPs^wfqL(^zma~OQUS7P7V7#W1FJFd>2ey@I z3C#nn#a?I`gw<2C;l{hp*spMRexG)Ygjwu-t`f-UkTV8I3iqz>(&E?v!&W#%4bpSk zIv#&HKRS+aDp@7mx#Ma(z{KBPdk&@I`iSsa&QE~cXz&j&K?3R9W_%U6hnx3Z&dHS+ z=F$h&W}wf z2HS3aY;rWja7Mu4{UYUTc^`)0Q6wRP2>b1O384Jg9yyl7kjZs?asn$?&VewYK%1a( zV9J(Sh4xB9J!$O67{Xv)=tqf~^n4uC5?KSTJ@M^2^ zSU+)@`XLs>!R)?bVR&Rhf>SLR_v5+I5kdVBSVPw!3Vd=)asH#k;8D~3AmPRexc9@VF=Cjls87e zqGK4?RyNQ--Le~;@u}plGL_8s6BA55cSQ1>fQ2w#6b3^K(t7b==YY~OxG?fe?0}<@ ztyJb#<}`RhZigub^J3ajL=qICVJa%&8htP<=7)yNmSpzZ*R=&K6tcxDC-dcf z(9Xh_KHmd^0v>(=aaD5Cg0Sel)ktJD%S30jB%8`Cnh5HhBW1+FrXfuI3KRP-%T~BZ zF$j}olO;3=Zmos4p4eHe0BRU5CYk;5oYrIy!!2Q@l+P803b_PO)i!tCq@oj(Sks1# zAPzxZiUUrYB9VGbp4E*wQDJ-TEd7#^f)SuRzBdso+8DH|yB0Ty;HJcm@$BFZJqm3S zKw$1TR2b&0T*aS(1|_;fO6}O8*KFRhu@g&^wo_)XwQF-3eT~x5dfyU{yX0XoVyHbnxloFaz>$JF@RXsD zTE;6*PT?xqIILD;!%DI+Hf-%;+_R0VIWOzf+u_jmQ+PKq!u&sH!di$3lWrZ#$$1Fry> zNedHct~hiO#M+cQD=L|9-?US3Rd-Z(h85E(b8%7qiHpkFeOTym>Nl3lUsBHBFqt3A z?F%~g=Cix8^_G|#aT}sz1RmiKhm}5rC71Q+88;Lq*~<2lnZyXt=q~enfV&E-@4m^2 zLJ|4g0?l;R#Y+7qRO*y5Sn9iTUf0;s6pd4h%hcpn?^-guG4D&K3{>{yazzZa0u}LW z&hS~&m^|gBC7ZC#Twjl6kY#69Q4@*DlG={Y^{WP-<2qsw3!D5Dn$B|GP#E8(HcfDx zXcw5oR!G#72%~f)%z_-6i&i_%x;j!;*3|5Aw9BW}HJ2CE_gx~*G>CPKIhY#7vUQ}1 zJBrzjVr%)Nm)Xl?u&5=RGfj@I+Gp$j_5IeU(t%0i}-9eYAw2{ zZDQf38(S!p zXZy#lR#bM4FIM!ZHes!VVb(5OIJ~kwcP6bRIQa7AX(qeswILxttLTR1jdDbFmFcIT zp;U740MTwUst-tjQwlV-=0MdZh!!Z+7o%(5%!|=+hA0AER>jG=s9qh*$WlvYGQ}Br z)*L#k=;>&!{H0A*Z52nWUNM!(TXx*yy| zmTvoP_iNvDsn*?f(=Mjl6~w&IWyvfd#jM=jF|6#eW!W~r_jAtYGYp9C{=eV$_51$+ z|NZFsobx>AInQ~{bDncQpJC4RtxU}r)4=)tjO~O^kBz_2|7t?CVcZJ__FUMT7w?n` z-n_V^vZ}#cS6{oVzI27TthA=4)@feqFxR_k%vCk!oWf%BirR9=u;tWfruQfYRWRJppWEtSL+chkbW5wjsd$Y>I;p#$wM#Gt)E3nFKq>Gui2AbRy0) zm0oaER>&Rvm>F9#xxCa_3M9l7Wk>PYbOHt*8)NO0>jmK>5x_5j)5F=|f*u<%S0xKX zr4fD|9OYdgaLU^Ty2(o$8VEjO#;XZ#n5T*ItrGci9g>jYaEk{M+lZgDn)B09R687% zqXI7IvGH;^s%y(+op&RWbW#~agrJSFnO9jwLGs9WkHJwL)xZTkHlj;Rj(av$j{P&7@W~=lZ?c#*H>cR420_BkSo?3 zlL5N5FAz2L6MbFX$9jCYlQ$m{WmWcw-E zOke^kabCvUCns*IhJ1T-;!T<+8*8K2rUD2Fj4m30CnsKyuqGB^6Bhx6+e88qlX)arBpGI#39sh;#&vIji8qEc)_lO} zYH^&p?uMJ}H@1}i5NH*xT#YZ1LP?j+KmXD1hZ3M4=IgjJNXyN?q8Q{?x16h~5}wbF zg6-8(eW{QDb{zGY+X=nw0hq5mc;8lot|*`CeP>_ZW^d>MBm_vu_19=240=TklbkX zMiSt8$Fqw>rQFlHJUwHyL^q)eS$r@t-V+_)6Z!QLQE(yw&b?cA!_kfaci26;0LTj& z-tlw<QNZ?YKbe@D|sK;57tL__3rOm91r1wQ+iIPb&cu~0v+AoAKyAO zdO}ZHm%E1=1b7H}Cv+){kq2?^0AEudIy^Yt(G$Je)5!mWFZy~`J?-8di^>QTsG^mZ z$L5cTgNBI>SsGWljjLSOk`tf6;S9juv8kdWfVB|z9ds;^aF6E@DsCT=`eUeL1Qryj z0~8ZZjrty~I@X&LU*su?kNcoHKK4i?D}`9q@u_f@qbH7j2S4HH{^s`EW5F#CGWV6l zYfzS0-%1Q|C`_z-w=GzF)W>{YfC44E7!niNpQAuZ0RqJe6yWVam6>)5x`-wtaKFta zoI*{)&>)@(g)j-NlL{0f9;1;J&y_+&qT-++H=-a%Pf#&@sF;L% zP~c4N-36k#h+!SpR*9_g0(F4GfpH%3`zH!nq|^a;EK?JEo?cIMQ(}eimj-D^Ly;^$PfloLR84D~4A=i=FcK`zrI9tH^UZqg4 z2pp;5aRv7~fioX*aw*sN1Q_~yz3K+yNi*`1Rn2B&nF^t(y+!!6bhNiTkDa z4Iq&~V>8}&1{9E!5+PfiR`{ZyqM4f}-VRB8InWf3z7y(8|XEK zG|Nev=RxK{oyszJgf)78^nC6`BU1$8^{x4l{wFcFaE(ndLbuME*fqEY% zb=LrzDrTt7T|he;?8Td?LqQ3+iR8rR_`8V%_0m9=!-FB^`Nl%T zq4+(%P{PXHbf@n_Gh^u3oJS(tQFhefT&lyla_?N=MHK3-09~^g{#;nD8RX^(r(dN$ z71@w+SqLS%WIzf1F~RoX{(HG`-4@8?TY~G?W}MJH zp@V0QPLXE;5nwq9Xhat3iVK3pp!^`3xi9WBYBC7cp}G1NLmz$7{nQVybVCyAu5*I= z;coQ7X0mb8%FsUeFisu4N`Q!^&kBZtP{iTJ)AqTGf; zQnT6&A6c2*yBG83L`#Z_A^$ezHQBH%i^EEw*c(#-04a1Wi=?!DYa;q`x=y1U=`C6Q zzPMeUu|(V4e#fZ(%M_z%rAfuE(};EZB&V@Yv#T#>*XhGwtomzTPhStlcSyvO-Jq=d z`ASBGx)4JWm4dcYp+mlkCv$>n?1LrvEJ00R3&MQ8!L4uNCX&?ajJLrWa-a3%0@ zHc?4Pw`7Tg{7)dgk&8gSS4;BK(?+Rj0UC&>|QO2Mh~_xg6EOGufFvAk3{O0qJ=0@TtHH!lN1q3 zA)xQUa;zD59vK>%`g+o0Vj6iqikC!oC$PPSZxJ8L(fV%xPLGmDAit9+w$LID+gg2@LmYDMNZ_!%N}C%ggzZ zUSxh8ChH7_$%cbrsv#MMk!bqHk~wwq9Rh3@p+||Oof`B=n&$ntuju^@Oid_xbZ>ia zM~i-6@2>vX0U^XZL4o_2yItqrr8}}4ONE)Iu}`Cr4lJ-QTkh{uhBl_4V{>6PDUgP<?iH>7DF2@Q=gS@GhvYBT zleDM82Y$@DGGKf6(HOO-- z1GRVc8sz&~Adi)-Pu0`a)7i&*KJ5Ll_p=YabQ+Fgq1LZgNcwuI%Xz<}uUFJl+G*Dt zeZ9c?j(!K@5?uuJ-uoG6-R}8LNh#VR_`lw>doce2?$96cJ-Q1P(4(D*dPJSS(f5Ae z`~4f`^ytRXAFCj{=&&&@=JOO2h&{0P{*k=HrheNOI+}(c#UO-m@;=tNJap$|sp!gM zn-4Tv$#SjxTh_E{ULcMYt=?F7yT-WnedE^mq+U#1Kfl4VFK7d#)w=(Nn>_mlc0@$| zYrr!OwH=H4H@m+NcQ$(Zj9dHBt>5<|*tXRBTQm&?hBQg$_$7e#< z+_pp!NAzRZUr7)JfW&I5QIr6Mp~SuqFdBGc9zrl}m$CT|i0%FCMVeF5nA;yh`h#`L z36aP&0f}z@90M(eVr-1W(!keuBytfp?GgRbQp}F{{?7$swC>4Vf|%aV`m~ksKlpN0 zB3XY4!hPC0is?H1W#9X&%zZsQ;~N1ViEN^`_8z=@B3tq959#aeSM~LR`w@kEKDfJg zC*079qUd?qFr4YAMnF{wD&OUxKo|vKz6a6%6m^UJlUJza;GhFXC#aqr7^;ni-)DfW zXyyD~?hX|UP~#K&NMl%iX#9t~`bRgCvO1iXP~Mc<`VH+6D2o$Q8L9-0{+kgHu~TxN ziFQ2|&^}EWNGjQkz%J$yu6pNXLEK=jH0R^QD3ZeQapJgpqUY40)>F~SMGY#S$Za4O zk8eT`sm6aOO~ZvZKGY7$jL)}Uyobd5)49b<*OGWnkM*1WW+jJK*b|?!qvNolFxOCl z!QL~L+Jf(?C|*QTu-qHQ#Z$De8J0#$-i4R}8#AM7xqZp{Zb|@;7ic0!3x^VPO=Mqv zTCy*0mq%$!IROTRZY$V6W!3WZT_=a6m+cc3CX>yF(BMw{I<(YC;5Pe}iO+`efH32< zC7dEq!FheLco)zVn*6?464|{wRhh5ve64lQ&EJl%L+KZ&wbk&7(O2u+2;n}!JTZjA z8A7=Az_;IcVk!XzZRc0C7R%Z|Gu^8t9d3gTAW&?7sMycp`Ux)IC;jJYf3u(_luJX& zeV3v-;E9WPP9V|Abh>->2$YC z-sol`#xQZJ|7FG00R0(RBZdU3S~Kr9&{{W#;o3zoo#;9_J(bU$Sp+qxkR;w2j=3uV#Pzhw8H-al z=QSu4Pk#e)k@fQ8LnzSNGxT;ILwUVwNTb5>Wv519U7rT0QVLD;=>+O=NgT*31{=tz zfsDf?7+U@X#V?+O8MsfDfYTDMuH{<4`+M!GP;cDBx@9mATdcQ^3?#45M)q&(v(ack z#Q~J+`-8BWJnUKOm6^;BWLBc7*gRrA2E(EqDw8kLKw_0x+hNq)v<`nfxIRdKat1An zYTns98cL2_k734DyhfQXxa-La(ovp&>KTs)wA`UoYDFgOv`Qq=DL=v9}=ttB}n_ihV1a-6@zh+e?P1lwt&1lap9=Ilb5 zqt#<6GtV_TPq7B&N1o6D9XCfKpCX=*2@P|bKgJ9I8f%62$hdv?qN`QR3G<0(rbGv<; zuo}MSF-qj#qhI@@$b`0hy|-(;b9F78pYi#(J-OZ9kd{Y?(Az|BPwv5{?@YKS0e3!plNPzd{mJb6=4D?H4VtXEekb|s)?;c)jaisvX4MJ zFdLC}Qwhrc;$pl!9TAa>ND=Gyq5`@0PIMt8`cdPKpsXvJdHJD$f%3<$J;R#(e&hOE zAuMzOWf*^6!v*g~`F#%}>k1E7hpdY`zXo^jZEy77Nc|fWRtFM6!87Le!R1hDXcsi< zx$PkJ!`=psH{IQ-Q{$}M##-*8x9=)qz!4%veXsYngWee4vavLABRR$z*n5lXxU&A> zjj~1aYK!JhuL%S9Xx?>ULg+2hk@{#D6DPSQ966+C60b}W%EWmh z@@NKO<}ZlpdqGTzx$kO)>3;$OhwaFXB2d_?Y5hgac4?IUS=-?E&v2jCthz6V5A8kr zR0^LvI3MG>Di|AhQKY|nCjfu{v#mo@y@8GxMRL7G5hxXvm9u*m5&iq+l0%kPf(W?W z4-moTAS_qW8p1vhgyqaCfc06w!+C@gVg%QSynljxHba5_m}pp|ckD)lad3^L;!J#=V)2oph!4lW%!Gc2h6-QF8?&4*ow1Kcb~1j4|^#Y(p6ab8cZ zPz>~ZJxk*$icUvNNJbj)^@K6z=|ok`gf*ExYP{n;*<{B9GHxOfJ&K^7Z0b@(Eaiz> zqg*jHbMN!_f1`9pke}?2%RoB?5ZwqWjP?O54zM9b@JvV#?as@s`}-r19=EKhO?G1( zejhE;^?CDt=arsoX*~BOK<+^L!D1*9w0dGl@cwQE!P^7MhjG)m?l&m?s!7N#OIDL{ z-AkkwEh&vu17XF)EFdo?hOQGbwey&761uSdUdSn*0Ok|_ptVgsr-^btZ$?p(xT7ADqr`CV3Qarj(D5_{4@4tq+))|Qve<;&KXoQ!VHu9& zR(EGY+GozoVAVQme9CG1Xk-L~JBr5O=?U!P0kR$rf`cxD;<{&K2J%?@N6MXF_q?A% z&WJ$m<8){3A7zm~Szz3;mSie6?sz%`K9r;<&8+RGROi#rTLbzD&VR&EJvgP#0Tq00 z+;Jym6A(t!S9>vLeYycHXfZn=)VL%|*KJ5N%v9@usBE?RrJU$P|Yva7#G>nvgw86(CRQpM4=8wdN**Q?(da3Xfu^yKue1PLfY)ISrisr zo@;~U!7!)jj@zd^|FQVmh-yvzL`kgFd-zq-3Lt%1czI1=YsQW33a7Y0XJSOzx z;a;@|J@_VIL0-QfKjl7Y^SboDE!Z9H)js5~ zVcOy(%flnLYb;(Yz1l|*g-|NrN1*1iRG8N-WvK5xBK&g-zksm&HXT+Mcfpjrv-nMT z7iKFj@Ga4gw7-JYVha?rhud^?=RJXMih6JpmG(q$x4nvZ=GDAHnPeiJr!#0X6(>d5 zJw+lc+C;i;>wz{(ha?+VzU(v^P?RB(IQ~ zyfGRCja&B`w;oc*5GR~a0hHdxy#Nk%6V&C6G!2T29uyZlC@yYLoOw`OJcmvjw}NeE z0syJ2MRQ@>BP8f4v5hTuu{|B6|36`_)m?R%B*lPR5e)|&)ED6@b-+m;x1~XXmKV{H# zP6lszBI^(t^^T|O?N0(??`fG8-nNbsjpc@wLMDR7u%;ZAau}=sO*|5#;3m!*gV!c5 zBu3FqR9w)ugBB{$_?|rtdNLMo3=w-HIb|n~JyE0TNFq0GeFvwO#;sanD|JcTZFKYo zCcHh}j0}Bqj?;;p89gf99=#O3c?-_uTbvk>*I*FFWz$XZh{WCk&$I?R>Y_Ct0SO(n zZA9}Oe9*QTn6nSswgG#NgSKNRlJzk90FCeHw`k*?l2_BmMZ(5FD;k9&iZ{BC3nOpM ze)wk)pTW{xbRv)_l_c7EmPGBtO0@6X5=A2lVsMeJfk*@LG}qAz%kecWSMUvnHw4<; zg5HU1#|%=y$Edf`f6v1cscIR*{as>JPv2)8KehIxNyXSVx%h1Ba%(#hPc%M-YNsCN z8vr(^+P%-#^hOh6^v0(!9opY%di~b)Y|=9 z3Of@QSidR8<$lqc)s#{Vf^*CyP#5)E5yA!8WUR7tgjMf>OUpsXD7zI zIcNidI;HpxPu#U+ze-%VPj2jr+{PdmrREb*NPVR5Zohh64E!-G4?pZ8{vv_ zN?1{G2|EV5cB23H^OI_zx@zgNvNC&vy?j;TBS|`U*E^O~H8>sh_6mH*@32?Z zRMY}n?ida)OOstRf6w+^U2pwv+IZ{aDHU__Zk-&?fIRqmcX!v*FCBmDcjGHqn0Y*Q z{)!y+73Q*9R}FG7SJXRhna0rtwY7El@c`f&9A(qY{2_AK;Lrr-@~V~Q>cRA!bG@U% zwZc&z#I?TESyd}Ca8|8wWD1T=g$c`RXmZV3KHa|Y+dbQ+Gj?kZFUrQA=7q^Cv$wx| zUFt%sddB>kaz{l~4R|(`uBfZ7s##`U;c!;gmYc_ylWeo`3kTyTC!#>B%c>m>=8D>S zQ8x4VY7k`CIKwO~Z2V+P1HYU?MP&2vht@fE$f-r;oBlk7FF z>T2_^S-+af;H))6+)~I|T2|=*!(aW9o8~Z;>*lKzCP}lZ9y~pZmt5@3{U@*=6%v-O%#Bn4mo@d+sw7u)^ z&ma5KXI(G9wCDAY%W7*Hoc2HhB7N@Dx4vrI+?YAOyzRGbTMjWWGY36LV&S&N zh1fJ8=QA%pf3R)S%ZDIU*VAoVxD31B+b*Pf{`9+l-?u|R;p~O2U4L(TVSGb4GB!g$ zT#&?yuTyLZQciyE^|u6&Y+o zoJklp`D!Ps=gRTrS2Xh1O^L!w3bFyz$1v4CNW%deQ0yi%O;w~FPmUC zC!qI0Q_chcJ!aJ}L(f{{G?&+|CZg>$m<=S~=iZb0wisxGasa;`Qf6z9)I z94lOj4!pW{mASsOW|>1tTDTy0-mHScg%emwr2}0RRinA80bNtAxnTu5FzTe}$739I zh(y-t#}ekx%~4V@R#HoSUUh9lgSocC+)%ZwrmCU}zdBRqFej806c!gF@1oLrXVp2W ziexIr5-MzLHe5N}I=GkMj={}Ac~(tYH6@vT#D=QaTv1w8?W%W73ujs7<&dnwJig9c zS=wN(sWn%vC|%~5gz*c5P7S(&1y%JG7%Sda@$>qr1agTi*?@iRMnnmJ+i-1!sC)}+bS$ri{tI|!FNIq`oDZaDe=jT^q+ zerml8X9quZ9{()A;bc*8v=rJl%LXHc!$U9+;AM`oDM4m34TP!Bwot$Qf8n8Z9zD{n z^O&?oCymi-H1zu;Cg9@Wjy>l0)0&JPGaR+gBfx10nQbtu?hQ9+vLjU2!kdi~f8tjH zw{N50k4ZJrSZ+0F*dhe0;Z6a+N`*iUZu;b#v?>?NHifEOYfK?3SEEU-ayhe&YFAx| zdV>_+0h+ANtUXzKvtG-FU{^<|YR*Dl6G5`>gNts%{Tlf65bg;1K{%4_6r3KoSokD= z31mB$4km(xbnwHk{r(L^pJmWBt7b-OXWgy7OXJq6ZVQ)%gKQ`~(H6i}ga1{*^vhoZ zfzS=1hw9o49HlKsx(ne`K6k0I40^Y^xYMA^+5;I$UK`vJ&`{Zk#;wZkL=;cE7^o7& z@yUjWGnj5eVo+ERw+3z<;`hL(w09H#yVO|*lUtK*NKn0GFo9H$ymVQ6!L$fu+u+QP z`~BO8O;fbcke6*Jnqw%)Hx%U?3J@;lZxL3_8Nv}`G5C<(tOw6tnP)S}aW~0vmnO>) z?bc=+mdr6M#(SY*QNCeGJ`by|<=$&C;7kk`{~M^;E_GF{B`Q5q_a7I{9N*y5l8Yqx5e+j89pl@+fdC5N8Qhi z$mcdfWwKG+AN+ojgKVGt61a)LEu%I$e3@*jKMzx)ya?XL9zwFkTm62TU(iD`ZiZU~ zToNIWPc7_;@^6Q03Zf<4LAW(Ta6aG~hv0qy&N&2Uf)49|Bi+(NdDAnreUWa+m4J3V z5o8-ostR;i3nQ*k+7IbsH{#bQ@g<1APQ(`hw-_#do8Nzbpx(~1TZ_s!jGl5MR1XbP zo9B|1?3rY(eA@5NgHLr5+*WH*srqcWm0lfz4uH4WY-+P5AYTNx25FDWX@{}VSk;Ek;^Q?Kw{ec$yKs2hIt+R<0lPjRuSAy>QXkvm!DOzXfi|vwr{7Vd`)e z^*y-;o9byO0a_i@$v_Eaq&-RLpM$Lj z{{+xc{;8n-8GPa|kF|M*C8}yPE~To@Sq7!N&VM4mSUJB%puJAck8IP7unjI1ZUf>L z$~G0;*2x?_hU9F64MiR_JZDF&HdufcW6B!v*tr7}%|IVMFh`jiWKFXo)Gw-n;4FE* zhU90}sbV`zUXX1BxvAzFG*ou7oz1|HpoDnNRo7(er0Wf!T_=~1mt94biIE(WCRMH+ z#I@IR@hOn`9DmX8Ux6^y*)aW@WsYHDzQJ<6VPdYqQel{g4j_A&o)_aCSX9GlUh?~0 z#P3=@mok{0hPb%~bCDq~*I>?;<${uU9f7O_^wiG;Y_bS6GzH6#m}7{`Gng9(rKF3z zJZ%sB%}D#V;Iu1{mh2h*?#+fc7(}s*xH{rS4xd}Eiv5+mj4u(0Cmu7L1+EUbO!z@| zy;?EoBA`p))`Mn)OmmhVp*uc44DADRBcROOAswU0cQbhPfah(*a}BkpJnHjSDfyDT zZ@?wLBF_mBHp3l*TLN4IeDnk8*D7hsf)R5jJTZr-`Ql5UNhBIPRClq!`G9L7#6W(l zk(f`GXGY{GS+hKl40oZAh7F@5SR4+6knJfe;%bAYldqvbcCLD&a#EhePgy?z{9pN;+BP6oB^n6* zZgs}f1GqK7Ee0+p2)7xya^Q-BaL0hF1Fk#>r`d@$Cveq4xCG$V1Lq9FwF37La4!Yn z4gz-sxMM-MAAn=eVjdQROU7knJ#ZS(D$ioz;(;6Pu>hY3w*;;R?oPM|;GTrr33m|g zB-{^hBcEd|5$+u>T_=-DXyo8j-2!~5X(zal(PKhU#K@uRlx5r4!wu zX!epctKQ)#s9IWITECiIrJC(Car zCg^y*wtfYjn`DbqdA91T7?k1`m!qa^HM>b$?2v`es=(1WPI~5)HY{h2gr8qi$z?9j zZ7g%t(P_l2s%q$k-J>n8b~x(T7Hvs&0}8W%&IRx@1@Z}reA)r~R4`mL#^$h6XKfW( zsoh>@w>xT9R@LKZg-HTlr65K!dxOJiuW_uh)1Me%7cx{1|0VfYhC2oh#zrwZRHEOO zpNzC1b~QLqYJLL7*jmjB$BMGL)jTKSR9aVOr{ixHB670hj00&y2cVT0MP+CeB3ELa zY%g;(Ryi3PjZke(Mb$DK6xnM^QL(EiJyMJbj#*Vz?vU~0fUifcPWvscs}5sJ869I&22t>A2E|Vm@u-*;m?3e^Rf&UtdtE8r`9O?a)Fdo& z0dD%#);rlkCZuvOI%_MXDKyGoN^)1#F11&jXb3QkO2REx=4jYl4+*x!m#bv>SsHf*ZH`EPDM^-c_)jp&lH-ygRtg1$5IfxWF z{QLR02L3NUr@SwM@NcYSC2J z)L&0+pZe+4AEz2qV^Y&ovr~&x%TiaTKAig7)Q;45Q%|J6lvXuu^)z+*)b#t)_ok1^ zxFMr2)#BWD!O_&eRkF$-O+wcKsF&+-S$amx>u3$3Xr^)l-U z>q_eb);FvNtRGnaYK=;~DzPN-`oyxt<%#aZdlR2ad^Pdy#6yXFi61A1Cq*S)m^3!& z(xj`CDw4cOPbB>@sXghnq(e!6OVTFCB#%#?lDs=PCuRMVKTP@Kly|4BOZBEcn)>V1 z=(I6u*0kKTMQLSeEoo1rZAp7IZExB;X}xJD(mqf7TiThl@M#xMvrM~X+HKQXr#&^T zbJ~GvN2jrLYkF$>ob;vXx1`^e{#g2N(_c*gG+mo9DI+(dB;z+3Pi5@P=*aj}#-|xy zWJG3;&zzW9n%R(fN9L26FJ|t|^kquZM@+wK`h(N|IQ`Y>?@mveam@_(jDFIE1#1o1 z#TAyRmRl?>(8dAF_m&vzCDsYnJnQw=8mrg(YwI3skG0SGk@ZvS8SD7Og2c7Z#fHS+ zC2mXXP7H$uT$VIBDJ|)mq}HTgBt4VVk+c^U;7j@{>ANIN@`&V1lCMp!NM4z|F8R;N z*QNYw%El>API(qO@lE+`iZpfl)SRjLQwygqp1O2u^VGJfzn%Kj)Q_j0n5s%mNS%_p zAhjyBKJ~WL|4e-%bzAE5sb8gjm-=YhZ_@sdwj-@Q?e(Jvajj-ebf4`^=qp((U3SPF*WgO*j**` z+?M!q;(Li7CVrZD2AaMwX*}+MW+fFSEr!k2Bsr6Qm(-7zmY-ajyexS|vNQSi^{Qck6OlcJpxJ0)pK_LRaYbdZ~Y@|rF2mITX0iv|5g26~P~=rxvD$}N?aYD=BP zX=${qu{5CR&kzlU!IA(MYU|56RmJ{27sU^jrj>Cd~w%0WVi_;Hsg@$^GeF9}Nij>cQXvT&>t zln6M1zZl!ogYkmjQvtkrGJf*sEPj#beTGZ-9|1hwm-*_fbpEMB(+N2|sYCHXj-zP` zp8iM>KM#_5ig-FI7jkSv{MmBei>>zItip1mEsrCB7-yHt5fh33=~b zpu{Uh6zN7R4B%66PZ9mI@`(7prAmD36#PJds8~)S__|go@k%;@|M4um;Mawfl@Q*P zW@Cqf6d>X^-l@c&%CIqh&MPw(e0K-p2j}Qju6%+PK2DU8b{_bM~$Pt zs*ED)%PKSLu;EbUT7f&K=1I%UlM0an3$Dy%HLkQYrbjy$Pf)iKE`^j;me#Y28W0sm zmrm_YrWrr-=K#Ysp{^R*UBE?I>d@Y2{fk?3}` z8z55l`s@*rm!f|3(p=YyrH*>sXcikZ4Qz>EU>Zo1k56@IQ;V)2mqtYOYpExmq8Ue0 zX;0xb)t+5AwI#cq3Bjc7TI^jmI)zlPhso0B)LYma0?wdgI*YL_$qs~#{srWUlZqY7W;d{R1jTv+ zXEm^c0&FEPw}HJYz=sITX<+XO@DVAuyo&V*V1ty~P{n!$uu;lg+Q{A)z~dlmWQPT? zS;{SIWJd(BManI0WPJkIhBS5Tr~sama;xgt2Ljkh*=9GeV*-3h$`v~LP{8fTxsH7# zfZaq}+`v8-;67A}(}Ayt%IC>nQ#G*TVftrK@;6x6%Wnbtch9#s;ykZec0el*!} zSdM^N#u^b7uF>G)e5sRZx1gR*X%ddu9pMiR@xUK6C%%t+0U8bvI1f`u$(m~yK2goXYAO5n7e zpiLuMT+R(qTD0oVVWHHj1FV)hBQ0m$SRfHT> z9x=`m0#3vrxa%t_%twDRubAmpM?|jz1@FynjTjY!aG-0M=P09U`B{YNAku4s^A0dj zt%(Rtp)BZiaemYb@S^9^pUJ&C%JMk@_z+|zyG8(mgqW8*JHNPO9!z7PGuGurMN_Iv z9>I%U@-^NXrX{8q&xwi)1v_!PQqSf_#gR=$D;wU00%jL4iJC=pv#QYTQXR9>C>_~s z^aAl=1zQ@W<2`$XeQt@pB!4E0yCLe*qewH*BNyOHO13y^GKs>?v26Lw(gp{+F^c!< z8nV!`g>Jbec3D=NCn_Xx=zp zDHk-+=!2>&m?P>!5+asqN&XO7HdRGbD2Iy_#ImRu`a2cck3n_C)rB+dg)^_t%`UOe z%`LG*nYnzdm{(YUW|uX)n327y?z>>bVWd%?k34s7*35!jCY_gPPSzs(%&fUN3-fbI z@|fzpv^mAuMfQ@c*-Ud@vISWM`8l$%p)ipAIa#xF?fA3P_PpFId^p5x-;Ig3;Hs+R zz_(A!s>;~+V{ph%72~j%Ev>F&X9TPwxQ_iGAPqtF)n)AO0uJG^rS+BN_3TH1(gC%i z3TGm8z~mRWaKe=~%7;#IJR?1D_`}MQfD8oHF_nN!fXeXwDpL!11YitB8UaP~Sf&-w zNI=yUr6g2{z{CPmyRxR1g$j5yV0@j#ba8qraU4ZER#cUhqy7IK!(RgrO_WHBK{!$pm1Mp`Y! zE7dhCzaUqsujtCS=!LkVAXOo>)ecrIVs!)Tcw_@xAtLlrF{Y=56@~mLv$Q%7C$?y9 zH6kyQl#MxPX&D+I8hKS&1FIDgBNR0Rmqk?<7kV!WtCTY*t0c>wlaHxmN#VTfnC^+V z=$%wl`9xHi{XHopLDvCE*l*ac4qS_o6S`Q{l9))~*npL3a^VpWS=m#UYKFRu@ zlC`P23r%2aoSsG@mFmv`wh2J3qBZf;a;B=^19(ONIu+e?eO3T^RRVzL1YlB)1KD-~ zL`&4!6;|-x^Lc@bRow!D9Ri3`T>)UH0L-d2;PXcT#H*G7ctMDmpn3OjoP0+_7I0Pu@C7x);Ff z0$8lN7G!V4#ZlEPVMa28HUxPSGb&X*q%M;Znd;5B(Dy*Aw5(w%%zptVEs>qWC~yFJ z>VvrGUl;>saZD7FakKa^*epH@Hj9si^crp!#|0qF;?DvQX7LvR2($RB0HV1G_~N2$ z=u~3437ily!UR4MfG~l6A&xMClL8PX@TmZV34A61VFI5EK$yTO0SFWLLa-Jl@TCBR z34A30VFF(ZK$yVa1Os6L-v~gMz_+3Z!URqWpop8mcLG?%P2hV0Ea4__MzF5rCh&s* zs<{dLU6?@KxoQ+urTQ_>yc1QW?MJmLHHz_E3~3XvDhCk|`yy7GR!#3F!ZQDPCG{RNO_8f>1R*avCa6joGH zSY$8AU65N4wCG0Xiisx+jk=KdgcY2ZXvBD8r=`yW!tA&UfNL7qJkTYM(nbLtj@LB4 zh|+1wYh5%ngzGhYwIqYuNgzoN!^I1H(OOkL%ryz!L!+-HUJLNL(uJ31$cv^=nkVsM z+I{r;3<701OP6J12Ay9rGpjh4g=&Z>>|w(Wq*o6_*yqeID9JC$!+IwtHwPqFEWp}l z?)*7?P8J@f3dzC-mwG{oJ$Ft~$#o()cYZ+u=3qePqw2Vy%+%!1OU6Ws{8XkTKl?fs zLOwzXX0XezZ81xI6fq;A~GjR$8qC_B(jSzukb^#|zWzhnW%3?SIY)1+VLKjkqFGgc2 z1a%ZcFKiS6w1gc^f#R%Lxmok(WnIU{5ST5(aTK0koI5WkcNSKpIczK;3h>i1^Vmfq zxRAo*C@gR1#8Vin8_s(?0rM5;GJ}YgN1+%%BZF z;Ij)X8we1ZU}jEu-Mrjcn8{HXYRsLtfb6P?!2E)O+}Wr!d}ufT&B-dh1{8v8$#q4! zgaF7w?d7$rs;bM&O0mAvFX3Aep>z+6-}up&%805JxV>fP_m*&Xh3TuJZiR_wYV@lk zehJf!2_(2RjM^c!(gu81?sV1hU!T$cOnW=Xrk_TPXe9b|rmxB7?RHnqz-_2qK zRy!LnTbR9Q!K?)=GCDQ^12b&qFxGiAO$)K3nP$yn#?xvOZD^uY z`Sg1Pe7PxIHi~BHp>}Z-OPZu7HHV6OSlFYKCUb;w0rDO_lFsOmys^q&%A^#75eNa^ z%A_eAG*X$e1B7KnIMt9U6PDRQn5ux~t847e>IPo0u;H7*?3s1j%%_p!mMO7qh3KST6HWi+D;0-4pAj{tFbFi23jh{Rjts^uy4{nh8F=Vz{wFc-w z5o)W;QOR}Az|}eF>hb$g#z{&U>&nP9**wy+R^7N9f1Y6=st%@($YRp0R$YzAQ2QM0 zbuC06R9u2#6y52}`LpeFvP$yM4>M^#7wuAo1EsEZHk9MnAMDr#E_1LLi7NRl0#t{H zA31D%gR54KzmUggD;c0g<5H@<3O^QwpNW!6u;B>yS~dM#NxVF$GJIVTLx1b1EEYyg z0X9m$S!qffLHmbyQ3M4vzo9|D2^4IUe2vd!8$s}6L_)2A#mjd#b)!{$^ASHT%`{Dj zU~%$Y%cyK~4!@SDTPK-&D4O>lqo}%>-Wf&EcM4iVV=jLoJr?I6k+LG=?6`%9pBRCy zBt(w-2e%=04^N(q7CCbi7*T*tw&Sd0pm7h>{T$=)F}7s(G19Fg4)kJL+BTb9e4g90 zp%)~dCmIaN%HAc06hPVP992oFQ|$8fIcx-=$HGP0dL|V_j^z!J-~E)VV@;!xsvNrJ zFE-X*x}ss3xM!+g5j%lmxk7Wr{oN6FjOGF4nM-ke`)d^Tr5#REC)!*C)8Bp}C0R}J z^sS_Zx_a>=sGZ>Zx`sdKb`vGNn07zj zd~3Ofa6I*Rsz%;l=xezRjOX`iQz9V6;*Q(L}<2 zX9{e7Bq(M};Y~n8B}8~Ne%h;SIaJL1I4Tm8MoYX+OLKAX8Ci#|F?m5C#Yr(YV}OXP zE6P<+W2KmBFsI16xg`RHC4h916t^2oj{zhWCDJ%4j@FOtDqOE6GsvD-QY=PNc97&; z5@lobs}ItK&n{x@G8#Vd+j0&EdoO}|0$2jm!(CQEJFVEGI>gbbKqV5o21gx^a-8=N zuSIZk;V%E)EEucD0y;EK1@D@X7Ym#=0XWrvgpC<#0j3GS?QjW6c#W_D<3+#&hv)MD z1pGJzlyGRzdSj=6&&W@Jdl=w@a3gyFY((I{;4Ga0SOHac?g$%W`G6#ir2U*y{R-GE zKx~E^c^`oN2)qWDKx8AH1m*|=Jp?=p;By4}33vs7PC^K7&VB$T2p<8<5nTXibLBEP z{XNK$x=+J{2v8^41ro9?)hdkIzXei#2}kG%!A6(JWC{rsH}_)38ZJO#>4kb^(l?P~ zP$whrLPR1mv5;E{fEhMHDbxX25zj3++^1%RQD8bp#z+?6HButpI?2NHqYy{kkl``} zJP1^R@Ina44AX##0;Wk$ieUq1!c0E{cn$FSy$Dc8t^W)G>c|Z%@KAG-k;=0 z3^f#HU078!XRuxWf=G@9l9nn-9}!7ESCS@UXu)1`9q)A6UGy6;(|Kl3BgN?J0a8Co z>J^h5QmB~RkW#g3n#v$#5>pK{G0snLyP5K$Uy{+F(xeSlW?1tP7!09Q)S#+O7Xp_+ z!J;1!F~Wq>WPvyxZp5Vku0vox0jU6L5U7TOBJ02+3MsBcdJ;}bWO^5HKSEgjod}$u zz|Rq&KG(2CCcj=Gx3bYEfqM**y#86T(v!<8=)f_yatd=J*#l)RZTK2o#yA>jP^RCn zVmBtzQW_oV80um9;r2}<1xhof0=pW_E`z!;_=!dbTf*-MsP5N~shfv^y?!Nnk5Ut@ zy;ayNxYk~hJCn_u8~75(v6QJE4vUMr7^?>@Wj=v^1GBWeqM>{h9b3V;=^&KaPLzE1 zrw*J){z2!ZbMLR#`lmlbmA`>TSKO^yRqDFrQK{_9~a-w!#q9G@0=cOGuQ9Cc$;1e}fV0TP4 zcy~-Q5d{d19J-lB#Zy0Qa8T*avzZm5Y-SO?2Af%*!we+sJ;<9`6cn3T6r#;6?qf5H zeA>+7J~p$+r_C(##by?T#AX)91vaw?k~gzB3E#}3P+&8QfWT%J0fEgd0%$XfQ_yCX z2w*cy1hARKNwAqE5ZKJ(2-?gN7~0IDkl4(kP+&8Q0KS<;fuPMS0+r1y3J==MB80q| zC4yozi^9rg7KN4VtMLR3Y-SNOu$e`W*vz7kvY92q@@CdR#K6u~jzTej$|cmG%`8ID zW)}CwW)=m{-pu0LU_qN%V#|w;$}qeUploJwLU}Wb!aQmI=h{OuovLIf!)sZA;1Ec6NHN-Tv%Wa3PVq#p86@R zddWsiXyheC2$m3ptp;(_>>S4px|6mvhRfYkgS7R7B`0rr= zXByN${(BhU1CrlJ9d_VMgMfd@Fc3ZtrW&dtJJhT*(a&^AXB{&D6X@dkF+(r{eZGoC z5Eso|S~BAhVxl?6F~dDqUN&qjKV|TYsz4;BTX;tfaKH{Mtz~JM(ts6`*X$-?0~xF= zE33w*(n|Dt9<5yg9uXI6)p_W?wd;v3fE)s@B<`1J)s-6o38;0yHc z(of&NUxB=SfP(IXuL?d&lFZs>6iKTtfP88(_h&btYb0UD;li5grx)g8l<_MFqI{I{ z62g+a9Fc}BMJZq@jxsNm+bAfZ)Z46ukJ5&bj!I0#kKq z@KjwL*cg?fB$|LIg_Sn2yGY?s?{!e`zo4#T!1k|Du>npMI3konwRiH0u7vfFL4N2uIqC80S4=P$c@Ckxy&PZCR{5Psz$H(}8s{Ug%YJRnvsbj|; zL2l}W`EzH|Dn)$(?Q$e3k>xyqf|7h_-|Q~5Q8sCG*d^B zD4~KY?t>H8&^%n?#BI;V1UT{^Pjo^xq&_XCI`^qCtvp`>WvX*tq9Nw;8W@)YsxJP&K=mqfOeO*BF;`d^8IcH#UdLUTqGgq7E0!ZZls>r7}E@*E#zj z-N%R>r4~o#AVppK1tWPmlG{ik zt-2MI0(C3P5~zoO8g0iRhkZ^7lk)iL4w(*r&Rsd?cU+2YuojY)u_{>#`s)k=SI&Mf zQS>1Ep3QGpBipM(PJqkHgChH0=R3|$_iM=4sqfc;6OQGa)%g2EBCi@Ta$SW1gXfh4 zii^bQTTL`Kr282gHy|OY5toQDMN?!ZjLn+JNM>m>l1BtYjR?pY5fCz}j&m;z{}LHS z)ito9$On)!GgHoIb8SL-9^sPd>p_6R>(_8!qrZ#$x)H7LM?8wx#l&JK67bihlxmJd z_w*x)^bN?@1s6%_4 zjdA^-o6>1ShbQS`zd+?^HQ(YL68bUTboR&R2Bao&?I!#*(WsrvZ8GXs79mh;WE9=G z(F;^%0Cg*i6sS6&aLG+5H3}vF7i`XCM_>;LPWgEH9jfNQ#8cU>3A+s?Imd4Pe`C8w zBeOeu4t@3p{yC?|16%upXU8D}7de=!jW*CEen(67c-XH{3MTP8TB;|aC`u#Y6vxU` zPwHt}ry~e^)4$=6Ny5Ppu8K0%W)8DP0KtT$ZbXsV|O4E0y?uqk4SAL7suV z44PhOR!Dt0kJL%_3Vb-q)CoLR50K8j)Dt*plI%Eds^^Ta;FxG6cU9m6 zTc*B}BVr{S&(~G(GN>nVo^dRkc7B=qs!_jyXc5ZqajCCR8L7ncAfa|4z37+E^^E_m z;z}GgTQ4FQ-K2?YTZ%N1 zo-P9?(oj?by0OIwybq@qhmj-NfU^o1=cPY5(34=WP&JUJSFI$^q`iO9lez|kVzu`# z5h1ho{z`;Uc~<1W?uMgYCmL4#FX;bn(BtwKobu}dbFl7E4cs;g3sEAl__|LPCF@@! z%DhM@6K)e1;jBA%I$mX3^}3A%UGg^EVj<*Qs80s$dPjB4EIM+rt7p^wnj z<43j-tytQJ44gEoujjq0ZrH^>$ODUg-O$B;7|(pT6`yYCihqJed`!zUv19XLoFU4J zPjd&)J%oQWtnrSziAT~$!cb6iM~ zobo?Ld_g3?6(oGosQG17tpKwHyy3zaw70O)cwGT?25(B}f;S~ZDfog>J9QK)6t`2w&iqtZv2%PAuJ4BmPv*?xe{Dy53k$y;`2X9xPI3J+~6dH+@;Z`)3We zBXIxB%)gZ9v)mLV%~@zzsWt4-cHQJh(v(D6O(?Ee%Xmh zpG}{)6O(>dLW7-{1jPZ0?!+W4m$1lAOv3U2(>pQgE1*wyV$xRxpT856{u220PE7jB zBvfH1CVds~g*!0`sRG2`iAn!3_`RK&Eb#~i`#UiiKF$MpJ2445K}V6DnD-#gMEJv< znC+<9d8(X<3=|$)+W#5fpQ8%1wp(H&ChO!R+r4;XEDr;Pkxkq7_VE;3U-~W?Nl6;% zZ9x=Cx>P_x4YpuI0XJED8)_%Mccje4uHuoJ7mduDi}aPbvZJpRmwxB`2dIgg857Y8sQ&1_ANqrDWc2vo z$OO?_DnWN-sRX^@Qi+H!SSmqSbg9H-ZxEBrsmZ+?Lg>gaCTDzNF)Rpt9LD6Fki)^K z?dV}l&IAdJJ9-$CGf_hG;N>tT=S1-qA*wfw$(baOGDO$In4FUYQ00Wfn4FU(%@H^< zgTt7dDb>`7%2cX(?JDlgN;S1KtX;esWnHm)k-utVT0VPaLn1Y-T8!l#(|yEnBh8Fz z+Ssc2JPv-HOx(qe4xm*};SA%>)UIjDINchK zRI2jMILyTQ_I6@6F(ai$osz?mA6AMl6K{OTgt1C{xwTCaUim5$mKGM-t@6?qKd@GN zX?ypCDx+!0)OR7unbzl!V(mIKL1o6BMVI9vd2D-0;ru6FSE`J=ygWE?O@TdJ!>NqB zbJ(k~9n9dtYNy!WR>nQK?4!h;zBXJy&gJ6DxHso9h)r@U2!Jdyj!Fd4pds8DaUxs z*yxS&nkm1BwA^n6JwV;G*=ir~YV%>JUxp(_;$w^&<5XObfIhf<#t1Jx@p7h-=!iu?sm>JvyeK9W3xdSUxj>qm!KT#b&?@DoSR7I!UeL@ z5B$z)t0cy3#=4TRFzu=*r9;BySb24u)X0n>_NKgG82hKyDc)mb9Ih+DO$WQN4@*zp z{>-S*6I+)aty8l)!4inoFN_$0TtYerPYvVz04bQ;0sVtvy^3YS!_NY6cpG2m;x(H= zxfjB<1Thxk$^8I4U2ss!a*x3C2RL4!=YDwRpg%huPwp4sIU9~9dcFzIAvi9>bIPCL z*@a}c7b4^@@H_(O{X{D+Rq9zdcywM~$&5O6tc<-_F7~e4R!3J6rYvwg%`719o({_S z0x#8*#-EUywFU6MA@X1FSnJ`*q2S=Lmczr78Y}P=`_XMbddx5+E|06JAv)vM!f`S^ zj5{BW)9~c&LymU?fHN|zL;Mn|YM9&sP%9?@{JW9&?jl4YDN7My2~%KlJd){5dYIg1 zI5y&$kKGfaimdZ-9*49|3R-$lt>=_ZRRSLP+lagX2{|Ucn;B0{-jMB>hXV;Vh-yAX~#JbAxEb|n8aqY0TJ_3oKk z_?|CDO^X2jYHJ;9--Bz|!-QdUaW0~o2w4k{@v33XMwxQ225=P;UxsJ?4a7Kfc3vZ} zehuS`_?&jbVCmjzbz%_PM?VM%IpeMn` zBZYm#>J%gYH6)xsWFr8vl`hGN~UXwe=L)tYx#dCR1HAXP6M6a4_)SN^YPEG0q zJBnze7Eogo-DHfOJMskCu@6*TpDyj(3W6lv7Y2mUpC4mM;JD=T|AvoeMN(y zkZfXe*nsD-OMu;$a!COf(Rbs@kC;JT7O^mJS4uJadRyT>k*2wbw->~WSz@72M`MX^ zX&9__V~Kl5+ZHx-T-Gn9&R~6x?@+%Kn>QYMWGXI85VHFO(P$jbQj>CbMQQi zXW=oVxDj0HLwcoPUC{dfNB+4;^)JAA6;FQkG%Xuzwb9F?sVg}Fz^_7J@@B&kn!|%z z>yTo8JCc4E8m~(|J1J2v2aru?{=dM%2KX)=YMY1FKVW&*~e*(q?&hcQ5mGm+;rU}8zvG)pPVZ3uA@yY=E9?JKA z4Z}B3n#g>uQO24MsTA=>d@1gODlC+1`N0BlgzM*_vnwo{sbHj zU&=liZ?~^Mik^EJgRQHN)~UR#yK2mPig?PO!vy&iM)$TdHprTH$B)4>mjJ~$JPSfm z(5{RTCo>M0a980ed1n1a(n018R zX8IB*{}|g^^g<)xCH^tCgxiHFh#8Kt{UvbXVO)R6f@5s21X`Xq+ZOEqlVfa|Kv5Vc z;~3kYpeI$PO6{_Y!)t30%gj8yR@3MmUMqx|-r=?M#Nm;{Yw5{@M-H#0rwAT7yq2Ca zc;xU}daBH{!)w{;9AVA3}Ha(FF0cbMMcwe;L&dWYB2b06Z!;kESKZ+eH<((^F0T_lIs()*|x z9$riTW5`(!uchaU#BGtoYw3L)RWZQfwI4^x{ljam%d5YKiuTT{#md7k!DsuH77`u( zK4eF~57~o$jQ6L@vQ_&p=8tgFG{UB0=B61ci%cSI6$j(0hELg&9 zb4_>_ECG<)^s`_I%+tW&gjbveOE3fmz0vS2Si>fCZR13zpC-`80z)?7jY3uq1MX znQ;~@QFp+`;tyr4h|S1qoZ*>$<}Hqby#rW+`Y2G#iHf6O9sl50oCNz3GAuX=76-vT z3&c3F^Ug=ym;4+Gu*fj;AZwfhOVi*)59HP7z~UI#@ea~q^r+Uc9Ov!7KN22&lp_G`cUGeGb!oqppF1IM!iw^oE5Ca7O13XETM7 ze|%mQg*y*3o|0HDeOTl=$k{#*8v}>b&6LAG{LY7skVr37!Lr zX5z``{JDSj^dq?}**buiFbIdsBAWVCoGsq%fLuu^rU`IF4K77gkH%Q?QPATOJh6Io zDu09c|Bhl{*^@F(!`sHm9IW4?lr69cG< zmCe?KFoj$vTzFM723i36f)j<0wh zC>dn=V<{KK{TJ3izOnNQs%+a|LWE5t%*vL_1*|KE-9}HaX@v6`!HP&b+%&>Dum9TF z2X=Ui0D5vvKj!z{9;GMTGr~Ly^@q8JRcm+<^0{Wo572kPjS(GP+f}bwO`kFrinmXD zZ56)*(3ot)*75pHhoI`$?=!?~Rtfi?JdP1d-cS+z{U<{rW$Zs05;xp`G9+61{*%H~ z^O_adZ_v1=5yz>f`~?Sc9GpsicWl{X=ujB4l;?Y6^SB}miwLkY5Oay#|FH&VRJZ9p zBH!1+CW6!NFaE*U@$X=t%)sZaR(!Hsfg?=%Q7=j|pu+#g3{#;N0wqC4p zQp2jh0&K^+0}W$(kpAvAy!@lo73&s+V`-(duZ+6AZ7-~wF>*Bbl$Nf>%l}~Mg@TpI z_+5Y(169@JP+nBAY~L7@`w-xEF;3y_S7S$Umt|ZHC(NNWP!cV^4gMl#!#H}4=Y+Au zb*!TxW<>NGuFA>!rpx?BM(>)=oo9@Q_|a1H4wA`ePaUCcRM;&j8=pV$E#I{Rf{|1| zlQ{-Xs{BUmEE)J-?w&z|kivsO3Zvy@w0N~y^fkhSCHVv1=za1)P^4v|FW^xFrOm#T z{G@@Ph&MNgm*f^8TxAfiGK)z7cZA9zqw?(jk@O?O8s*fPBYZAOO5Gnl310onQ{J=6s~H66xe3m0Uh%fkv%Kavsl%B+2;b8qo^TWY{fvR8$h^PzY z7$0(44dY=XS}uiZ6X`#p93P){wyDkn79V{%oRqtJ_RL5U_?CpzX?<-__16Py|0#9q zQv5Xzr_kleK_Kqo(=1iFI6G?>@zo;9;Q%;vA=w5~DB#5cz8;{f@D$vQv?w+8NGVbd z_ZZ;9)L+9vaf}3JlZj7DA&rwrqe8TuLSq8#6&fdv_h`%!V=!qN6`wo8_#ufDpIlDd z&w`qVNZLdsLBaf?N18DWG}y*GRj?ExB>Lraq7&_aXVMy{iJHLLWpAxhNUC7na~tpv z@Jze|p2rYYgD{?iSn)9cevXjm@wi_>h(h_@e}m%(NbAHO!a;7xN6&&=;3F*u5HrG~ z#7lr@(xNUx7VMYuaN+-fsqS1P`-3v2y3y@5gKdg=&byhd)zk#Kz5Vo$dxX>?#{a;6 z`oAIBC-8g>gkbO8;7hXfYOC0Xg}~3jx?BFP`=Mf`R9we+3^CM`&}uUE9mufet?%p? zRbtCs1q|G&b+Ve{Ih4uus2tM=!gIqO{|Df8Gv@MLZ&Sg5bNL7i=JMUlxqPIQJ(urh z&gGA_b0eMoBP2*x|6`u^H}ph%{<&#Af7hS1&DDDYtb@f(FhkOh%uOE*puo{-T{k+d z>kc}to0~Nl&;;l`<)58!J#tGM9u%_+`+YnmO*z=$GZ=^hGc7p+DmyAA&M3@*ynqA|JxSi+tz{FY=);dXW$P;YB|5g%|nImwu5C zA<>I`=#O0F!xE=mI1^&CQx3O|li za77O@RKXQJLbab}Mnv4ua~2}{$!6TyqHsgcuhEQvRo(?Xno%n^y|cR)%hR{F_4;>@ z;l3WKiD|6t=*kSk^*s!;gre2$^7#(8_y}DdEbNPJ;U^2;>%(B&JtoWY*G3Q37X;k( zLxeoT`=GbFT?T}*T%ULUFtEtG?ng4j6+nb#UI9c{l@Ts?#6>^^@j6JE?5f{^8M#gj zDtRjqLy`s#D`*SBxF3k%ao`b{vYKaF;?T;i`oc7yJGeK9oSUe*GYAsy6C%0Ts#{3mWF zV!>wmH3ZAn##@RAoZ|uYLCk%;uZZAzxPl62Jh$N1FkD(hNF6Va(-iUQBIdQ&KrcLV z8v|}KV%QQdUF;KT-QKMG;k?+0NiB_r;q*{mQTG)cv?3(OKVg-Z9x+x^B*=?|8;}^X z27_zfpQYypa1#=N>jNXuylYc^*A|LjA8$rt;MsDu;^K~8xrA{q??|F{Y*fWmzQ4jL z)(f{#Xls?BO*m)cnz( zhjdh=@8uEF{Brp7Na0?d6VQ7#{JlJ1mty#PdH#sJ;$rTwfrrWX_SN5}gAF_c7lk*i z`61ZA!;rE_NMsidHt;aCDh!34i*Ddy=!j5CB2lq{hccWD0;4gpfro)(#L#2Y4wD%d zFfya;*tD}4WzQogs}CeA_QErcErj!)WSlW8R`+&ny84`#^ znq_T@o_g3R8hVMV^EXXkc#GDfm}QOQQQA~}`Q_j#nr4Zan+>GMM;q}>{lTg@)P zFdS6h)rysv>*Un?3z0}^_}Y)c;hQpE#N&EKeT&0Kd^BtX_dd+9TDhc?YwmV*Z00SN zxOt@$3jYw(n>w#fJ%n_O!#38H4ewA7O=L&bf#|&kJ&7Mhg?ut+A()=H7|ut^pGC+h zJR|Uw>2z@zaw{&1izB&-fS!OS(GBEBt0~+Z@=pzs`(XBZ`?ZRW-0?);8?5F2mcy5B@Mm*?; zrW*TJb$4+ADp!9tb}ic3+l!`D+Ano8CuokIR;R86s&RM@RvuOLtkFAGZXQpzVr7>w z`Bq_aS?{h*Uhr-CcOm#eWbz3-!qD4=p88@RG8 z)#n(OsD>Q-4$}M<9^u&YaQp-he>vpX%ZO}jMufXHw_ZfxZ}14W90mx>UIpkCJi;;? zs%>(nfGjJ5BM%RL1)2LVK15DzL=MvtY8?Iy*d$E(urNico1wC9?;|<7l(M940J-R7 zDd!!}GB8PKwnb#fnqe z-zmisLW$_S49rfCP`WdiDu{QJP&Yy--5H38O743GDkLnj*=7UlA&Inj@`!Pmx4c#L zs0}BqWon+rygkqub++iryHM!!@rYtDvh!ucPAM+G7gIuS1}#GLWD@-}0)3I8JLK+V zNb@2*!rklPxDF40q8NJ-+4wPht+`@GZbRTLc(h_LKooG?E%K_CfJi@k8 zICvxunuu!<>pdWQ8B*YJ$hUfb^ry?$3HgU_!Bqg;HyHqs;Y712q_rF_Gw%t z&Br8|PGyXnOsgs4dxG#F&X#C`ls(QePn0qvMGOEsur{DA92OPQSeK%?kW>qYGu6?N z3tNiIx1nX0v`ZxI(LK1!(d%%Q4qF7Xr%obOoUju99li{yB`JGcz)QUz4Z^uyGNUmY zNltzi@e7S2qE7+}%jggmvUEN`r{EEmz8#LU@ZfhcWugvgaG#8EILQ_aIf^`<2b6xs zVT*PUjw|S4i*`F4x6(r?{1-U5`3&^p;jErfg|znI^&;BINnZf(eusxE=8XM+fMFy~ zIfC_?_i3jjr_HRLUOP28xwC&~SKIupZC$unrWM{zy{+5k_n=Me*x8Bw6UoV2{r{_{ zCnx()JI@f=f>GsUbW^srB4T@2_hf8bY27}VyDnrrqDEec9Ph*P1Rnp({~(|G|0`el z(|cxF#~WE4dvM1S%ZG1uTRQOBRt}Ll!I*+3%UF-FHEUNjq*@j&ZCJb(n`0xeruybJ zjrf#|i__6zZNmI(!>UCau#`WH)q=gV17n$kV=Zao5?vhUz6Qq~hUr>^#~29R4$;w@ z#@Y1sD?km>OX2X0)EO%l;iUgGLR*?vG;)OzE-81z5TAUW8zLo@Thy@ICWyOo^}6+# z@(BI%L7dMz%3 z*Wj`c>|?KP!o>(#$beRa(58CaaJiHNt0ik!$=MUQIU$I{(B_6UYnxZKU<4U}m#kjf zoLaUP^|YdKWg~6~4THexHD}@S)Kn8Lz-&MzlQRn&R^eLEMbHRLw+5-Kz@_tA0)~e` zUcm}ZE^b`10i)6Ym&|HhwFq}asF5KQ_boJ>(a@Y)yaX{Cqyk#hs4yJU5vlq!)~eAV zsGf)IknbYv^@|rbV|*KA)3RbUt^-89ne7J@sgTNbMEAs#fRensOMyq1)y^Y#ex z7lN4brL6_i(*apzTTA`YC2B%CY|)A}i!phZhPQByIx&Rk0;OAqg)18|nHNN=U%YHB ztjF3_P=gifq%Z*2rluN~h^6A)imNcq7e*5PuUrCq*q|nd@P_qG)cN{_EtvTWuu1>Y zMycE-jbPc7^q`hV(A4yxB}4>^B=3manUR;fae9gGY7*$4MG=uv{~tBO}MuiHR;I(ITc{e zPRHb0%@qwTAdKm?V2frzH>n~mQ&S5!q?*=aT4YWbNCjyI(Xif>mzLC;)m%-A?VCaT zER-e9t5*ur+yRkvgTlLUTd>_TNJcjiX$lZ&tT09kFj=R1MQbXGF@Hb|zxh%N!eCL- zrk15^mn>ALhhQzSGx=3cj8EfwFsd#LXTE31-JYrGY`R)-Q&$>MEsN^ckX>Hr!t_ut z>P#JVQF@>s)eBvm9_mG%qoX#Y2l`RH&?RB0F3_~;bC#^EPvPoW2ncR=2}st#(`U_& z1TW1Do;7WHB)BmQUN?R55?GqWYxQ|Uo&j14gV?lKEerAL*R4kugUS8!ArR9=e?=B% zq*JjnEf6IR^k7w52r2P(X?0o%$_a%HdMQn5fs7{Rk{Z(3un2N-W)^}bH^gX8!(jRu zNwj1kWG1mD#PC|`vvA3H3OvT0aA7^JIIDg`3p!!wAQg2E(k|Mv2r!^!?USu>D1f8;3f62=M;s; z^|%Wlh!ts*Q)gk7WCK1N1cAbpmD5t|8tYRqKIeoXt5&mdXsT~)u17CLy(0|qdyecL z>mEQVJJ34`O)}R=8zV?wLC%yGKyAtnS=z`xX^Yw%26?6C1;45_1Z%nB`dMux)s`@f zMq~Y=rDrTuZRtQ1d#Y*=VJxh+YHY34)-Xitsb}%ffno5rFxczmg6oSLy|ceyl3}Uq zm!q~jULTP;Yvgx^$eJ%APS}MXRF+@=XL`)p5e7A&BbQo70b1MC)X=Ot!yqxZEo&C3 zt`J7XG%spa-63cNq(}8AJ#6AQFm2fMgz=z1Y^rZYx1QZ2Z^-2No5*Nyj1&dUS%|?O ziy*-DR3AOy5&y!Lv+A4h?I%DGG8~QDsd&12-q zBxPh3Q`VJ>u^uc8#+{m2RVo*#-)Br5k0~u{5?;AM(54-248?b(ovr;U?~4X^G1@6a zLP)BoRd(lp*{DvMpfG@UZccGsBaZCAu^h2)F(?Sz(Ta1&x3u-}Tl(Wh5vQ=?$|t6C zvnJ+WjPcxyZ#{%pfhNWa@+isG6p%I?RVBp5?>BNlh3yyU0i&b<(Ut8d`k+xF)Z-d= zkA7S%Ym^N~B+{X*Db?cv zC}A1<;V|~{^&YNzaZ9CwBcF`I!dn4$to0Pux5HV6GY0y% zbad+z+94zUDbV(x3YpW-fn&%^@?@e?cAe+oH(-{B3Av`E!`4vw7REdlsjWK6}oOy3ot zuJtsp1nQCh)-MGt9HG>Yhby%a^k7y^eRgooD;Pxcf-^I2Li|k4dsAm<#Ho2h-Z$h6 zyDzAo=9$#IoWB#zGpTun18H8)TdR2{HP58x6=i5%@xYo_lCF8B)Q9a5QL6{9TxC+t z$_OaeYSx1*-TQIYMwzTTz$r9PGLuS{7);5ujy=gV<@GDNBn#RXOZ0m1!%a-l_{>w; z+A5h!3G~3C#Zwg?S+=3|&=+0faHX$!MxvHQf-dIfZR%0^hrMBVIi7`!89|i0hz_ebgS3f-|J2Mk)Fz5%79Cg3EY8r(l7Tg|G+i^xsF{>tYbRg(@Pc}Zc8FZx ziQvR}fO?ARw{g%^ez$Y1jlQN<45q1GbNC$7mzQM6G;v)eUazC7m424?YlgIjzL*+^ z^K@%?B1wk^!mC@uMizscBk}{4{3Uen1w3-|xK>FXEcZ96(%gzQP`-1=hIL>zmGXccQSW3PkY5yGIw*;^`Pv(@R`isj(D=A^Nnqb z4R*Tz*G6E@Q2RyGZ*na_<^2O3N@0JKj1LxsW!~-0QrNTkdJ=ev%(En9tZ+?1AImz# zCEt#JmIU#6yX#-EBJ!VSN&NG6S4&B1Q?XI|fhvOk^vE#|IZC$(6ma=J(-T>}{ zJBm*R7@Kb}_|&((5;>=c3gKAK9K~`eFDytYx0H&4d*l!pFNc*)+ zRfR{*!2&1R-;L<2jCt$r@317!8y$_c5@-&7iT-XCdRypzrrnos4#HaU84Q^L39_U5Wz0c8>nwoHQ=aa z6sRabh)jTBzF;h(s{%h!%omV)8^duO8?7Sd3#KBH^<(D76+zbN@Q&wI+9vxutcvW- zVNT}eFzY5k^`-_b#?a|+4P^y;*mkQA>j89koPvOgRmN_QDBVes5VfbQ08xgMpe1lf zF)UTYjtY*NKy+@Rn5r(wpDiihf|N4?r{Pp(9e6Z5uuolB&@BbIE-kR5v+X>TRPD=` z#=^Qf8oFiEUhLG{*01*GiG{QdBGl_cX@bSP^UzW}Zgg%8Qwr3{Vy&A?UqrZR_vEmKtnp=u;mp><0pF07gQ%1JSFW z!$E4(n3}x8)PjU|M^n0~@H3Lq1x#r#oIctnoDr?-_2#0vLiHY?43h@X>u4gKRQJyQ zo}Dh~+sJg=HHNsx%4GS45zYM-RJ;#_1T|HSl}ks9q!fxKqWUxkSalN+zhk#M14 zcLF$y(5%}f6K$0wZZTt!xxi?}E~0eR${knEWIKpNeGiaC@p8vXD32P`p`aN9f!*lU zcl6?vHRq90lm!hH}#KM_(D5cSF76nBp(P1F4upjPEPB=r&{ z6B^Llz1t&nrsVPNNFJW;;3;JuW*Vu}B?4x`qJou(+aiy3r|aoe>>!1}LaN5$eW2lFz6dgJM7P#BWZCbBhf~$|b#OdJ$4zkP zMdg#V!Byr1MMiBZTRn}g3W^wqPbdYln34+B+~_z#A*C4-P=h4{IKHM|b2mFSTu&Gt zTTrL)u?ZBlA>*m~#_Mw?(OjI4EKkET?N7j=mZ%D^KdYQe%yPC~k0E5V#J-heOYC*y z83MX1pFk)TM`++EuLi0SHG#`m!i~UshotfLLZrcubggRH7_rT$d-`{&WwXSl;$)vx z14hit&d}a?$z0^M>`cw6ypkpK?GW33sL1X_%G9Ld191Ec9J7%DiHRM`bEno~tYlw; zkOs)GeKj060LA_&9Mos~AvpdD2dRz{y?~i11Ztv-F!1(>UQm-x^nz*9toTGPsMB6e z^a3foc}Nqzpv@;bA1PC-ipRjQ9*$pT5j_DRLi9{Hrjz=5IH>OSS#Su^(<#39K?qI~ z*4m$eQ-t?%I7E1#ghLDOICO$Ickr$x#*Q*~aJ+Uqe7XiYf#mD_XuADX;aJ_Ccoheiucia?CgiJ6E z>j6NG_3@A6kGT-QK7{fjTJubJE`#GDdQ$M*4#&;(bi%`fH9toW&p3V#jvwL4WnvDm z>M_7}a@_e4Jmv6|;wgF<9%kxtP;WLGBh4oP8H1ovc+6uFPcBiM-vBZPK{N3FdGr1~s=Z4z4AQJ)NB%qx=)ZfDfYDU`+CT5Z5JP7;0mQ4#UhD53pNmNL|2(^(Q;pcD3zLjT(g7cJMJ+ zRol!4=i{-Z5qe~)ikWX1l0UOVLivWM0(_X0fo~=WU?w0AJF$pLABZb7Dt#Y_D>O#= zJ`h)EjHmC7{)~KO#pl+Tr#Yu48cXL;a{wtwgIE)RGXg=jqZ&&g@r5c;W7FkUFm^&m zMn%ok-UQP+y?P!*GTx}nstd6KD#t^WFsS4>l}Ic|SQxJ)N)0QXNO)NqNH%Y&dY7>e zj(jgcAr9tlsX;hVusyC(97RGLW?GA#G+Ih#s%fQWHNAA!G7^LrTZ`g>z1=$@AGhE=V1$3rQTG zP6sK+?3JH@=8k6BgptOy(q`k~g^9J5>uZb+iL(;Sbz;I;3x=NPC0}E)>Y0AB#$xy+ z8+Ey$CMNQs1%u#|SQ0u;u7O}q4l}G7MT*JY+JIt^(G?GAo>Ge>=L$D#gdfF5iCb57 zo}@RG)r3xuPYCl@3jl<;XW+9h)Ph*0Z3#EXb_$sx?^NQMN_AUHS(mzsVJ|6AB|#_zz$Zd0S7}5?%6#Ki?i=*i;hipktu3l<3!ItijMBL^ z4q@bgtIb?&U~b04!cy&W1}bPSfLR)oNY|J=>MKi{Pt{RIda|a_>1mEu6)O}4WrS`s zjOd#JD-bf_U1)UvTls`lFRds!Wu?oX-wx|W%z+KA$=8j6>(@?9Vu06T1JDi!vYg|K zd@j@&4ZU$%&1D7~Y-WV(Iy~4n6=7(Lr6EKR?k5Zy)B@d36^5kNjME7e`EpVmhDkco z9!pakq*9j6(Q<8EE7B?(MWBjT`i)3cVE(+2YOQ#~((96DV0gN8`%U!-&oE_F8*|BM zm%}<`Ag*Tg+kIW!(b4pEtEH`6vKF?h3o^{M>H<(wGl&KIZwO?_;hK?tGirrArPkQ# zg2SGh)P}1$h6^Qi|03@9ZItd1dI}-^!B7P(U%DceQCksLs-z|<4OLApc@;ZF>`O$A zeYMHa(vzD`TOwekXR)=J%7f^s{2keg^r?gV! znlf^{+?T!zU-~LTkp`K%)qu8ha3DRq97d~7L!#9h7RW)Gc%UE7KEa5A+n14H4b;TR zlTltOHMwk{qFvC@UhNAJUUwR!S1sV7V(XqVctI-148WwCJcf+*yPGiEX!ese(t#Pr z4%7IwqM>UZYO^ni6R5(L5Ij-q(TUn-PGbM_B#aZ-qeM+HtCQ30C`v`(W6Sji75U6i)XQYFD1tL8&J-rm^Vs*|+18DJ? ztp^^bgk8cp0fWJJuOEp5PLrNL)19kv(FLbAp$pCk%vfU+Wxl8g!WJZ?5>Ah{3)+y^ zML|)r>(lIurgC8d-HQ^JNiT}V18<^H7pFx<_6>N{67n;>cN(g-XD>|MpYrU5KQ4l< zuz=ZCITWXwa*cxno-(_TaVeU<&le$IylY}m!J1(#l-CIfg`?1K~++AMV$E z+H_TD@WZY#Qt-hn<*01I2s_5o3}{xNQ|yU_0R6V1CbXIvt!6OJADeFMz1b+VnVgLp zA2qewYM;Q?bOQU}p)tYSRoFP3s4dHzH7FtXYh1s}9T^zXCZ!D@b!B^_FcD^5jaH=E zFq&gG#nFXvLwl4Z#mHR-Y*0Vinw%(8^c)?;IbAc@@eAYl^9Ya%J%O7al~`@A{7&^C z;^>*FNRiMz42E>YzO5_qZC9yQO^9!{mIZSO6(OH=U#v2#Xf@LJtIAT}YHwyVP&vOd zh>oV1DPPJmW@WXWj|lRioOlzR;UF-=1FxIL5n#wqe0NAI7dnN)449RgY-Jpy8``m2 zC&!7s59ZFt`}4VaIyNx89L0u{u|j2_6%#|#Bl672Ox&9ZMtjlGA&lw(b@k*ZK*les z2!^Ai&?*>SFunlQHOQ=3S{nq$p!X^r9ow&ROz*IY*+t=;mF~T}dd`RQ#5K8DT@dO^ zJY8Tpei7-$Ef$hb;&zGh*tFK!4qLX`p=;Mf4k}EUVdh*2ayBV2-ETPcH1jW7( z)`%jq>*Wt=eRY!Z7LnfxJxUm8>W(GVJ^2EE+R|@9r2eD<5h}gvqyZ_`(o+()<9fIi z2n@+73k+?V3X}(6&;EwSvBGcg(x$uuW?^tji}T;$S$Zc^m{}tX$_2g3AN<$Igkd=O zpO>ayp83kpR;_}NmxVqri!$3c-SkFyS)zGa8uGGC*OqQ&(^y(DIPWT>Do6HLg?d^2 zzsy%p`m!Z<;Ld0^XA48l7Wtel&g5)Krj+{q(2%o3vNTOfD+lMa$JoJ4l;2;E4206G zRxmDxRif^v%}(o&mwI+u8!AlXr_ERAgxsOw%4})V`dBCt)i*Lpt~JOCRLb@{t4`SB zdvtl7Lp>cG^Q}@my_dyy4Ti_B*}xR#YP*&bjubf|9%&MEmlk&3@6w8#hdNiF6R`Dk z4{c6iSd9a8XhUHy0Wo3YNKGi!YG0-;sFx&WeyH<FbR@*^A0T6CxHv_hz#` zG7e*dK`S-fZ;?j$&Bn+yGoo9jQCcrzIGF~4=?LVx<16$&3xU>*Jt>WrFBoy z^(^EHgGaePg|3p>=XgHRRKVVS%#DqnW2Qpd3FF$~8lD=jT8 z#|Ad*s}~whp7m*Cp5sn-MqKV#?h$9W3yk3J(Nf11z%&Ot860=3lYaw(w-Q?G6ett+ z_j5?aayJN~;~sLX&o~ZnOBlEwA@31yneV;iZC;YsI8K>R<(2}&>OA0B)@O{DN}USk zxXg*U&z3s5aL3%MoB~|iMv(Od=Jx4GRKV^v9`O*560x0S`_6^&ew});Q$F9xJI5)1 z7l=8>$wP{UIlj!bzO33Er?k{Ioe|b&iVdV`Tj0^NSQmj~vt8@QJq7@I*6oRxY{ap? z+h6OLR^Bqf`e0$Z+wKC-NeCC7>p=qUd`+?G-YbPR-AmkqPNi$@org%d)<+XSDkTXw zB4VZW!+yg_S~<&{9Bb!#G9gjx#6e%Kd$ltjfAiO)$d@|Ba60Z$CyBpt;$2fJXpVc4 zgP|m0F?YG+#P8W4IHO3>OTat((YHDI*4%c-M4hv0`yJ=K&hRac=}s-x)n^dbeVp%h z@aHN?P%8Fhr)HajgCJ}JHRH~A%9&Hd3#S#IuaYkeQL43nIGMp?>we&4=$ z2}GyDDFY^TAqRgU57l5$HuP%n4Rva`#ZKOQ=R_v;h+}LpZ5RO?YDyci9Qp{M%C-^K zy-?L>@eIR?MX?RBIdBX`xpwKc@}f0 z2xD^hIq}oMf>wmCcHW44!S?O>^zy=c~t;*r$MXW=q_&ey8fjkyh5 z=dQ)0V}1uN<5|9h^~qYWzJ#nt(MwGWC47Ba&cR-k%kk(GABW5M4qv6ODd7l> zK8;6W$G#7j!Mk0IUlYa~rc8@_v-0^W=Sv8kNCqNzLVs; z)~);E{irN=wPSPxz8sAM>(Pc3(O6orJoLw)N;Jp#Lh@c3u6hRgx%84iLPfR=6UjCR70n%K4?0q%8%dWS~+pZ8rf-87;liFH=vkbXHnUt znsICbF?c=28>ot_f%bVkI_7JPl#AZGvC9*Sd4ma*#T^q5;CTs;#I}pDirlE=OIEy( zhs)#r6!0E4iTY`{g-{RI8WUe895Ba~xaL&COWQ?3nqF5RZ$`r3X5vy>bT*Vx&6m{k zeatHddGTeqGfd+RiR2L^@J&2A&0K74F^2KQYtX5#eS`Ah7N9+e2Xh=ccHv~Wu<{(Q z<%F%_>m7W(ldpchF5v4jzIf85c|BjZ@pUg>5A*d^ymZgyF&615l+UsjK*%5%^>lwu zE$ekx2ou-4@fXDLh#Rj5hBf>VFuN4&E+&|a$k)zGoLopN<(FLx2<9ovokY7+12og! zr*R;D=K%$Iz~EEdE%y@2@%njA8GAAFy`Irl`CH8#);qQQ)9#3mwsM7-U6)W?#csKm ziT|W!8gagJ(pkO>!ce$g^beU@=N>FY^IGbR`G!-z1c1|>v7n%EnKOE;lROOo^edo> z&=+Zs=RvbpLKo4bwZ#vjUvajycP0JS?)dZWODH(He*33^fk#agq_j+Wd*=b^TE|82 z$SPU};;i1skBX*JdC@nR=M=_koZ&Y(d8D5fVguq?kFmp5Rx15J@TRo1%43gJvEC^H z4Q?CyPn(HvO4vk*WC&8 z&<@@$M+c%3;T5IG$=bpos}h|Z_zT_QDPYXobcZY9Hlu@szTe~Elf~xHdO+xQj3UPx zW>p?QdPwYUh{Y`H!F}lWYT{y_b|xUpVyj@DGr{Ve=Tut-?b;P{>sbvHoUOx{{~l0s z-9gfjf3T97cyw%Vayy*DR^XviMGKj5->0bo?x);k&~_(geE^wGV5Z=Ol|Uh^UF4Sa zRrEhHijBTBn-{Ox9a8L)A&ZT^F3O$Ha(^OHZfBNjy{f=<>*9U$$T}AtK-aoYxbUw& z7j#?yh>O!L4j_ShBMG=VmhkRh5o?5N9lP*Gl+>EJ-m&A;P|R`Y_)MJdFG_cz0^NABdxcib_!Yit zBDwdQ2inQ5hxXN$x-t@Ie8en*nLF&6&Qk_8Rx7b}?R-2B;}KJ5Jq?%fJYO#nCN^~c zOy~ZPYh$kIms~aDW$cS6) zNC#_!yKMa%GQ-`t+3E&7+6;dWF5?Bh&hvT(YX>bE?+f}6o~Q6g?3|b2GI&>>fiEM< ztl(=5Unlc5pRZ+nweTh7RQ-=g%tZ)jKLfY=zC%x%P@P(s>te7^;7j1ye;i7f2;VyHhf^ntr+_Ud# z;9HY#tQ;)rMUki&wIy8ZQzAvy!~^M3Z1-*P`PPQo2>$8F(^^@Jf>_pcx*yr6Jzs)g z+EzO~=+Wh(-Y!S4oQ+^=Q0{*lJbVq$Jlq&B+)um>E@KN{U4-r7>%DxvpRa5Ax|y$g z_i@Dnz8USEw3QvTN*YYPmejOf6~_*5}h71WZQXw8b9#C)(0h7W@# z2q!z;uw-oN&|b{b@x*djR)W2)-!F5m`%ydWl?($ihDLeIq4b65&7no$Kq=oDvl^bb z9?@H+?OtX2@FA@j>;$}!eBF5cEKX@okSM-6`6vDUf^fC8(R=bk|UDC{rAn4`~t-GE({g26V7o95)6 ziU?9-k-;fzOvm8ft;L&E}QMimR^Y!{e zlhqa2l8Q&id;~7z-}pL&u@Y5a`JgJW4l}s##q&8l5P;|&?JxjiODFvwHGqV-Yln`*!x14?0{ob!F{1O%)GbOT^L3O zb1y;Q0-3)Uzuc*5gjOtve4YiKPM12)o#&je+_BNmfr!!k6wL$9X|QQ$I>wYFCNM~h zo*LT=L2O1X@2Q10o#{xIj?*A-XGgPtbpHlufibbT2xupIrH8mk#inN{2GjJH~n&KPS}KVZw2!8M!ex)p)-UdCk();zoc z{-U+cvt}TN=}GtOfc~4(=zm7&zayJ|Z>|wDD%STj<#+5$hLmG~fY}eQ%h>5YtE*4A z^ZvBdf5q~lDwE*R;e9gb9JI#Bqd)1KF5A~0=Ja#s#L2etsJy<2Y`s(RH_=I8C7P(r zLhHPF(HhNj8;~e!@BayfaQ~<7Q?n*fc^9McML+GrtjdcN{vtNeuJzXoQTSZ+n+)S= z$C&S>C`EoDEzN5r&0l7xiN1b*WD3`?uG1<1zY8A;3wsbSUmz~sM~Qev^*falAr1NF*g(_=RNu0*Qe z_cQe>{mmE@5%{Zx&Jj{Tt8;xh+i0Y{0sa2myXJfCrnP4t8g(tflh{GF9+ixD0t=P{=zVt@j(om@ylcPu)6y z!-lC-7K|Aq=`Qxt9encR*GOp+zCJF(BWc^+a2b2)-A~sQe9eAc`gpi}og8q>iJJ@I zGTzTuhZnPcu##EBaU5eKo=fov1p2*RbFq_)o{2fL zRHkP4y>4b_k5KA;c)p59XZAC=j6d*Y;Q$9SkFN^8Br~22t}~MnM4^+j%CY)TjhH@W z-EtNI-EfW&O7XSUFm}H#3D+a>$MM8)B(Nr7JY2?1z7`R-g0FRa2?;zFIgJGCbe%uC zKbS4iS#x6gEHCTTgR$Cv8j0GYaS%A3jd&#c*hk?q?xyzvzCO>_*YUER#>@B#y_>v# z^R_{g?nq+Pg6AnbFX54-^Jdf_g$BOP!pnRIUt9S)m#_2qI>6Tl___`+>t?zh;7bZG zhtB+lrd!JCTC1X+K6it3#aI*e$snW0q(F$4e6W5+->-NhH`L9g1rnAgD!k=;xtxeDfF7u zJLo1mnBXQJ`4Q)|M;RDpSifR>gSjfhS`0^7r8V+-VT<%8DMWiIjuzR8m^WT%AeKdl z^%EmWAYiqx1LKss>(=5^l}oZ&`DnpJ%;n{-2ZG&>QHXUV+B6Z4%RsPoUb}lhw9wtXo?GU=rvS1rR`Ul4z{SznO1-)b+2KUQC0f6hbr=bwLg z4z%pt+_$%{zpYa(+Sb;(y=7;oYQdRstJ@npd1KPTO?_}L;t88AxbwQByQ?0DK6c4* zb}Ku&x_g88>)Lwz5Udt&>ff}uqgO7--s`2+!h5q9b$51l^eai1$$DXu_R3A~?CxFB z(Y3v;*H1@Nv9xVdPjg#eTQ3eY^61iMUV_Fg%qp5gIwGTbc?zZJ$>SiMu1)^cs!oYrhdzo-nPEJ)$I*k{k?mex_PdTP@TnqfN)&k)TeP)_q6pQv#zZz z!fL;WyZ|X==c=8Zo7;LR(FRNu9Qsr0FaJ*{m$kYj^J!wRp#lML0-xQ)@qN zGw0Ttb9^;U_gkPJG?c(L1HDw{^^m(zZDpj@ff&Pu@Ly=HyP)(e4@3r(^+iii*dr)wd+4*Up@wQj5FKLgYo8 zP%kMIqqkp5{10XkrU(b7NWp~#I8;;kh`(8UQG;TK@pny|UOTmRYDS7o6;Ioqw%XRg zQ+dntR8w~L^=8**M_22PoiJ%bhED0}+p-y_HiCn$N~fGl?P*KSiVBe*YW zGS2$l0Uva{4JdFSWuR7Af=mnIsm|_u8>SPa1SKWG;}v_ z$?GGO4*W$fZM|KaB3bxW4}rLJZ5#gdx5BXCFK)L(@^JL@cDKUf5wy1pfs)}oxkhku zN7sVmSc}wY9_jn{Bsw8Uq06t-K`JAt+@RJ5Rj0RH5vHKA6Epeu#KS?0|3>vHQmCPM zA5nlvoI&${>)F2>Ek!F@isQ@vNUjm*aV$2@QRj)p$G6mBrlIRNmYF9z^I+%WTj)rx z5$AC%cK?pPzMVbCx6nhj-zuOeaJ*jo8X6I4ha-*Tab)D4F0>yIpySKRNUjm*aV$12 z#ldAv$Gd+R$u;6Uj>YaopYHh9^w3J<(c}NoeM7lC03E^O$d^UUYf|k!)21F@-b8YZ zIFDnorRS5{*0uvP27Sl3?3r0-`i~!3!9mN25r8wb+&bO_wMz_dETHb7&>GP1^;(OW;-$A!vW;qnH5VXE8XXf zcaNhu@9E#ufBbudkz6Cr<5+BrqEa$l(e`)M!ljdy?(;QsV7<-Kxq%1zyirf)T8<;H z(Z$6zgvYthIJB`0NeW$Wv;(gB&l#=s>8HWXpEp~j?_%uJR`dAszZ0T#d>bq9)yp(= z9mg_9o4?6B@Hyi6mVTh@2Z9|>j^O)8>v3ko&`OATU`TX79qkawp4IPpQc+*5Y4FR;tIpziqDGRb`C zPf;q9v`qg1Lk~&z_>I{kEY!CU{v9$fAWYa7`2Mb#_xCG)A!Wk8(EGp7Ke1!M4eiHI z^2CM>-2e}2!Nk3KxD99(wMj{Es! z+T-uB{}f%OlhKzNqmmO6kbA3ZH2F9eS>LdYvjF^?i$B-Kz7WH5d?Xfze^S!;r1e|NXaU}xI4k0a zeW&{;7hb_{QI3JU4p`>WBUsODAFvJ}A0%SmV%@{YzSB$D{*wK9Ecvb4U#E`PUxweruzUzh#s&@p{lPcW%^C zS!Vlk=Rrs4uz!ytgi-A^>wtfc@vMPAFBzAb#+f>Ef~<3^jtGzrmge&wP%T87-2W2& z*~?ag+MW9iy!RW}E7fFQWLyOwm_yFmt5XO`ZMORM9kDm}1HLXT6)fKUxFPrCD(#%k z@_A6CLCm9d8#a|nC#J=7+;^c}upnHJ9`#H+ib4bMM>px=OoAcQm)|CEPV3m?U;Jl0!A(;nakH_%m@z_&1 zGyyT5B9ZHaND3^sFEP?>&X*i>&JiiT6;HBg$6{vP5tQxEF#>?bfA*cuLyi@H?TCE~ z7-i+aacAuFF&i9Wwep`v$=R<_oVw-XMtevJwVz1#L(FVi;B=2#D_HfbvaiwtwwC-hC zT!da?-EO79Z>Q-Msq&NwB;H{>K-2Jm@gWnI_eS$&{$Wh~x!rn@={(42XF*wosEzv# z(zM^Wkv581X{E65H=i-ZqT7WVkP9}(Mf4jvZS2I2vv%jXz)2$iQ1=n0+8DO|y%zpt z{p|W?P;o6yNt69UQ}a*wgL)N12kAC=-j?SR ztx(M@!uGqQBzGD27$WeWFg_uGPa3bt-&gQg0$wv7HW}ez^AQ{=1=V=O{5JvooB4zW zJYhaBfalGhYQRrTlm#t<*ksRU*s&4*3O8l;v#n{Y%Da5GBsE&6_DzCtAb zXJ2pJWB`1V@gvIBG2=P|gsSvnckT+(SsyJ`}q;X7x@yVqYCYy3GnFi)l>O<$l6Q3t&HW zu9QZ>y&8WA_yAfBk1zHoockRJ`a{mw@rM-9<+s2(CMo!-^J|93=(>WDB{xEu+pnGX zxd@fqp0Hk}nA@*fUqI)8*5Gpc7F&2=-(Y_ZRZG#;P{zH5%}cY6`wHTsWPhF6xy}Ee%tv~4?{&0v{!VV#zRx(48$h%C~i+_YnrAhdF}d1#A0 z`xNU?T&m!d(}3KNjrjH=&aUu<1s`B5&hP-`A*Uo`fcWXpaNJ-;S3F2hW^ zKf%^KTg6ePf(ld{3+eKzaa&EN30bzBtuJfDhq_1KaX-8W@mv|9+k#MWWrX$yq2kI2 zy&wn`7x)wAOQwPv7(i3SzXJ+$P@EV{g-6)f5CO5w_-BEF5(}#GxZx6Lt1^`Kw0S5k z8({a{0?P&KbMx7OQ~PVk_<#QXE;#UZP~oeIT~uV3uCwnmjv4kV#?>%$$IN4<{TGbT z26Mh>zGTX1P08R4o!f80GQDEIYTNsrk2&hpX!3{c&&Sc-{cMizn4;hMj4ORsh2G$o zV$w6l2qk_)T)JyK|0$&H8SBTE{e*qYR^N|;^Q-LSNf=j{2Tl7bSWx>hSWRW$5IVnM ze#f+*GQV%yKSE~%R`XJn=Ad=Zvaho4we0(>Pr-jf=zPL@!m|GZR@>eWGq3EQgw7{G zfc+%Ki1v&4r|f4#=RxP7V_$`a$9@cr4H68U-*CR;*v~k}9JDi+!h#=lkGl5F?p?0^ z2{e`VGiV-_eShFgE49%1LGqNRRG!6(Efl14s&c)b?Q8B1zOZwE(~`KcIpeRR~MxaqSjvP1HZ8 zIuOTdH}~~3h@ujzT6o5-c~TaB!|EaFTdu<81eEa7G+(Qg~ z{>#^Z4B zy-*2=yj}+UF&{_yvZ`Z|=5GO)7AE{<%POf#|M{|-1Xw6EsC(Hz@+T;lu?-06tgh`1 z6+`p6K06UWSQl3hQ5Qa+*?M_=#!}Jk`0|tfD?s)z)I)M?6mW2bnZ{A6*1fH%Z5^r( z3F*2J$c}C`FMu$Q$%vHIopJ&TZ!rTF2v-m2X=bfkTlAA?pA+7C`*^(loQa z3PJreCKUYq`eH3HxuI-JK(j$RnEvzYVkyA7E}lt8@O-R`JBFwWqOsn0;2}+xaS*P7 z^`aBuUVUL>VrFeAwV4Hi(HKg?`+-yB;{aW$_W*Jq)L(Pe=b=uA^y~mJ^99p?y%`=Z zMkJ90lq&P*^Dv1p&BHq~QN&LkKKcfD_!pE!^YA{zV~XL=mw8UNXdbphR-&w3l8;7IN833knh6T1c*mrAI=7VFQ^N+m3kgH zU}_}R?*L(25vKDuIFG{Mg%p;7t@outP6Xt|G|1^>eOWXX53nyvgKPvOnFiSj$kYJh z%b~6daMeIZa}YTUN@0)-=|A8024x=z$Fr-C2Xe^T&HhmipFn0H1oRBv#@z)3Er&_O zV2>gE`EqzOKw1uW^qsqNQ*Wj|UxGC1d~P6>fqsX8^MsEh2uA>E;(3oMiCXk|sM8_M zANA@yOwHHjBtTN{1LWoN@ND3`o-P+55fFjAl6YtT$U`1luW<(F;WxlQ&BMEp2(iPT z&%*}*(md?x-my1z%Jdmir_PF~^v4(tXHN<@1}GO(ydOACJ`MrubAZ%AkkWam(_y*J z%}Obohe@Q9VZ*)t>KozV)PRT6Nl@00JbdL1@Nf?*OY`t?q%klL{~aL_9`?hO1sqKE z?(9mn@9f&|b0e$dIAd-Pj2+T=5sznWTD#lY+u;Sa2kAi~02~Z(;80J3p!@uk2~a-+ zq=?7IDM~MYFS9PvATbo^YJ}*NiBk?p6OWFQSg9UO&(Q!U)!rdAUKc>%ke=Yd;eISa zCLx`pFydZ19Pa>Hhwej2PaSZM1vtKbY5q_)zKrlQm{{=hWup#|*VC_!z>#tp&yR*P z?dP|Z+XC6xmR_MA_8L&2X(m0xklg09bm*b{?0q3Q;Jgu*9i}iO6D)sQ#+NgIy}N3delzDI_k$#sg#U9aZ_@%E-q!t zNnaBDy3l!%=0}lEhVAvb&vgiqyapH#`nE1eS#S^`285M&ukvJn8`crb~-tO3iQi`1w8IM(9`kL|Z*#NlJE=|7I+*w5DE zxJB?~|0o;DHy|5dgn2`D$ZH$lX$kqVk%P3fY($O?@G$-QN=ltFowOvxlQb5BGhni<(-$F0KR^%*yz&E`Ar(1&{~$u`bpF{(5zBAgGJE zz|N+deEti-n$Le6ASZ1zm%af$&q7+7&*AY2(FCtSK3e9J_y7>ul*2Vh+nNw`Q^`j2|?Xiyi;2+Xc` z*2V7u)^+ip=?Qo~%KX{?zqqRbj_az6{=>AJ6rfZNS2I?3%VpO-U*1WAKKzI#umwOu^+vsi;D|k zYL%msorrtOuU@wAnC)dfX1?QftGrRQ|hxqyhD1y>bOq?o`gF@l1BT7u5Tz_eYY9eo762DXSLB-BF?~V`<(H(qG`ESPqo5< zncHu>?KXNkwX62t8|VN>AW1kp&$#$&6Bpw^y-K>7zl@8&!Wj$+ync@(zp>W(GXBw0 zi`s6(|2`V?*kPQRE|0A@vGY9iAXm3>eqU+kcdwj}keTbgk1?zGVkYt%bKRa6@x>m@ zb(>ADn*zk0Gb9Gz13VRjv6qyP0Gf%5C{m~&1&{wfo4A-tOI*+c{6?Gp5pV8gWwfX* zIoG`nvMZY==eiT1q3o4hj|I^4bh++DATnL9JJ7-wqUTQh*ge~DRvj`< z&*0~DcK^T(?0y-Xa5}r+Hv_wK|4_x3egRCw$iIb9=#rTTxXs#Qz*&qjs~OGrmN%hO zL+013+CiL|t{QCVEqa!D66Dn^lffAMYLFKEKJH9Vk$X8%Ve*DX3tj{So>|n^ESIEG z%*IPGV3m!rjoGBp%I5%AR!-wr^Qw{`(NxjB3pl<PqeUw=$RiZS9*@WeZXu%N7tW~p3_Zh zl880bkxr`$UsXQO17sf{)RTg=))haz<*>&Ox_v17~GySJs`%lx+;Ho}w?o7x1 z@J)c6kzOW~C^{TZ)gR9+=6A!Z$;yu7rvIGWB>wY7Na*__{pZ6_vdZcgH3fYTjs8*~}&<97t>5Xko8+Vgp%a{=FCl+^3<@UY~&>l)aKO zKkqA1wn)zW9f&Vwi{#AzTkul0NZu)JZ($43^Qh4??(T}c>Pp0WA4QGyoNl>OYHh)* zRMeDhv$4g&^z^p4_*SvSzd_x&K-JJ_i|-j*+>ax_F?Kpk?6fzDz{mC(A6tSR^fueX#|~l?YVL-Q>9>3dme_R3iO&61Kl;YkOA>>RAQOGb!=Z1y-uT8xn>rcu>aR>*{d!Xz`VaTDl>cC04Rt0NGq3&w z{;ItC1A@H9y!vC~V|O8GFgv)7^E+n%HNR6+e!BxWKYFhCVK0vSMnBx!!VlSl^ZP*z zLCx<{O;R6+ zb(;Zn^Q!pR*~sfi2Tt42=F`WhXMS~6#rxI|x6ExA!va1=D3AtCVJ!cn#&Ff6s{kXCFZ=t5KmZQrw@Z^qjnAd_QoM zo);(T`N$0PyxizHdW)KQ-JlGfHE%ld=jQXE*h1mZ=U@5OG<1y?Ap}6$q+qw-3l+#8$B`un<|{vF*ppG^I_BS2gRm* zu7cUhZM5kdphIQTjjrGtl{Wp3u_**>(rD8YGqCCP@E?^k-vlbnZF(8DmsHN|Z8{#> z^vN06^iE^br_*{SGIQM7$nLbM?JK_|PV)dLn=%*j zmu-Gt_46gMSxfxp!(!7ZOmIFPZF&@2)5@lYEtA+U+Up5pFC^gxpC>Bw>Vw8!uLXn@ z-NqPvJ?>0X_Ifvt{6>4RD^v~rps;Uf5?_CBY~j8bQP8(T^GeU@ru(AAdlzt0nAX)j zZ-jghkYNJ}f^vxuW*;*U;@k_IGY;enr1NKSI$r^#*Fb{$E?lL?D$+tU=!k)k@+tJ% zkH3-K2}nH-$ej-493TfA$TMNodmPB6fE+Rqna!=&{Tw>J0_SZLuPXu+ac}FxFN8#_ ziPt@rPOhxv3SAK+NU|VF(lfVxR6O%qOaNLT%tS*k5ti>my-3%#qKNI;FX^EAOpttfd&!(x)0v9vwD~CXgD-ZNiknVO2 z#(`wc0}%((b0NG_L-ZRAo|qH&%^vp-kNaYe`*SUK{l& zP_E4RVy`Aw@x;fta#6CgW_Fu+ChuuiI9Ip`IP&D0mCvVzvhn%zWjq4OJPGS+4_Ef; z`4$c}M0?9Q@s?R}Z}+$_@wlJ$xS#R3bAymBIJ%H#e^kNe^|abNCn zU+Qr`=W+j?$9>71xOaKnmwDWu_PC$tWt%^sS5m}Dwz2*r*(T;=+JB4`G-E@X?`(5F z=tov=k!|dlB~D{w*4gG#1Z70C?Pe!}k&-1wd#N&R#a@Th6XkmBpc`JqZ zd`wa;MOvg-0G~euJ4Uj%@Oe9C@%h-8wd|!9vWsslpj;VV_f&Ev`}|_Datoh-T70AJ z@&=E$GpvzoRz82fP&PjQn8c|-G7pFp0_nNVdY$%`4)K;*aX;#DKjU#f=5ZhNxOdHo zJKxO`Sx4mR=DS54Nanc5ec0o^YEImbdEB|8ce(RE2M5wq^0@cTiTiPnJFkB^yfP1a z-1mCi*UyRj36J|(kNZiF`#m1_!8vh%*yH|`$NiMY{XUQT<~ecan^+t@cc1gPpZ2)_ zqvfu1-R>5-PV&5sT*>pX=+==tEK-1HW1H{VW*-Nm%%CrMQ+2lIB>C^SYOkJc9oSuuJXYH%n zTgJs(X2pHb<9@*7zQf~wg~z=zC+0gwCMIdLEMxF7bo@AbIf;&INV`+XkweI9qd&Cm7u19Rd& z?s4Z`XO1UicsGgz>DlXXKQt%q-0yWLck>->4kUA@$Ng@P`{6lpuXx;#dfX3q-0$bjo~dVYRoXg1s>)a`p)L4?NCBRWZN96@9bgdoPm8M3j#;Y8 z*qF8KeAX^WcG*2xK)EtU#a_u3WA|yXax%`!{o)&KS2UW!RFy`qS=DvzLRl)t?tL;I zfneVhxkSduQPJZ%IkR3L=^AhQQdoFJOat&MGArRJcaeL?xi))Cn1yK zUA)fH5wDB!(q4CN0ljW;23}{!EM6BIvzGm>Eo2w3TR^!o<6^I5kLunlR!+uQd7k)2 zTk_QbBiF3F?xelCq_4kMWIWLKCEl$C zCEjCJ))rhUQqYVI&9iAUoLSs^a4qOhSKHVzOT5R%tYybnSrT)d#QOrul^GU$CC762 zlvp_#XXP33jTZ54(d_Fd2E&IbQ zWEY=bK)EslVy|R>?kuVq?}`_e2ZX#X1WpSEg6&mF!X7!(!!R zoRz!9H=5P+T6XRuO;=H$6+NfK?n{KbS&c5Bey(BsOlNmHX0dy0%v$#IKqN`_{|~#b znt|Oni{@L{o$sww`f=|*rrzY3P?fDZ4)d9jOtBi={-=Cs^L z{g*iM8|R7}&3)7zIKm^o4UIK#8h4N2hA8sl{|y-GiQP{C@1h9j75Jayb{n4*cr)(n zR`*eFz!6N|(0GsZgO|%a(l5f1-*}I7htZFJFE)Gd9_bUf8JnY6)KMW7gWA?ZPl|hoe0P)U8$AHKDLlC$N^DOGs3nmKzyaq>(rR34J6MdIB$ep zn~hf4!KXvbUu$rD&5P{l9JSf4J@b+QWXA6vlDr|1Oa>!}vV=f-_G73TqG$8DaUb@$ z4|?3YJnnCqC+=H3?gJk8~hdl0m9`|;S`>W=O`=H0Y z*W;e{xL-3*-1$VVqt)(fJ?_gq?$4bk?(047t32*YJ?>A-orGrgoLj{8dE7G|_e(tP zPtFtfUXOd1$9;*%{nR{hzt-cv+~a<}$NlI$abM+e@9?-U_PBq1p15Z`?(H7;^E~c% z%oF!6k9*qV&Z*Cd${u{ASRntWS!TJ%eVNDoX@rx@y);kUJ3Q`7J?`f`?!)uMz1`z} ziO2mZkNf(0;-2=n$JSPAr#<7Xzk0e1_aMkc-|AKON2PT=`4(1#Xn*{pRG9B&#fUP@ zK3jS(AcMMUg1~5Bb9x1V`Uvo0r`Jz34tuCxzjAt!&NEPw4;#w-zC$6%rsN&ts@Fxp z*`o>*=uqnBM%48kO z?$l<-TRsLH+3!_!z5s~SNDA^@hYl(J9FPJ0<#~m34iNc*wt_6yn;cfF%lJgE&Nd3? zC1xWejVIZ;E{9#)XB=La03v(K3a1|sdCpfsZUIDHPl49(*sQjQ+Lk9qjrz^MR-R2AhnT9NZOI~cYw$< zVv0@&_6d$SIO_o!a(ImbvL`L}BCj2Q$eu8wvZlgYJv}Y59|q1L;97e<3dm81*RKKD zk&aVdvIK9#c0B4bKwP;x0Xb!K7zFq#R}g@c=#anol>Rw`L&$nS*tDKUw^s}GFg4f} zpY>h`99hdK{Y(MEShjw6J0Nl;UEzEQkW{KyC{r0Lg7J#e>+8Td>5S(Q_QF3so@~8b z06<-H5gk5B95juCe3-*rDc|)_+@A)HR747L5jM13jr}PgvVR&7xz9mK`b9w0^Y23W zUjcFFoksu}H@rYF^&>!br{g;OB_JmpoC}|gUXG+`LUiS+xL*s1EBmVf+3(QF0zwN~ zyT1nzxkj((JOIcsr`J~jc>*U@uOOE#;)beW(iqO#PH^Z;_=}bHeEr0CL93YHrTv@ZifT2_ij0AaI`p8~Q4 z5cW{Leywq==K0lYc~7arc>y44r`K{o+`M`nAnJ`AkPEn})T;ow2R|+4&49=oClt;Y zAZ&JoZ}%V{0%W~I=UzZIqZer~1JtXj!`=Grc&n z-vVND9U)8c{w+2;<}N@oPOo)<&@5c zj*DTHZ;A|qTu>+#>M6`LrR-Rk&sHiLUa9CawR*N%kH!p&b$*K+1=J=2CxJVjtpzz= zd`<Y|y5WR}+fsFs>%3R;exe9$WjaUAtn2 zf@Evg2kUNHyH44U}qf;9$?WNjOKRcTrw6K(Wn)6aXbc95mwYJG&Uv!1UvIP zJ~jgkE44zADk;{2TCrRKAN<0rGs9{wtia826jrPB)=Ig!GYAnGv7alhfjs3>t={x( zPzkG}0ptXo>ST!qP}r#m80$$J>;P%!G#RRn>emfnDXc>0Hz6F$6B{QdM(80V91YDf zi>{7$My|t%!(zJ}j1|ixpr4QK`fxz%Mvy3W3kdsNVIEzeplo$)Thw`HDZtkoqOQFZ z81yuVnLvZ;MYuSrQnNw@Ew}-e;b68MiG>5AgGwf=W(&2@j;B_?xfWJ8 zV0ddeJi&snu}@0@K31BHN)*3GHJm7K3;j;P(7-0H!$z_QvG^C&g1I%&2R9c>_V;?gZEmz^PWCtK;Ah>=c*>>kRTMyniS;%cw1yvAa zCu(EzJD8{iquceHlqHsee0FECFg6a-iI$$sc2#X-fwjp1UKEhai||N-r%OSH}~Bb+_ZM!#x=n}|E3`_`-{o2x^oa2kJ>Ed z!pIH6Y7{oSp<2&nz+8t*Lj%f;DyaH~ZoJ9RL5yPz+jaVZ)#18LN7)#)5o*AXST$NI z5(OI)c&RSQ>6fljR2-?j3OS{fjx-1lL*n0#wfeR~wOpEjTM7Yq@c`_ABvP(o(GzS6 z>o=Fiqbe!CR`SNWLJ_k_Bw$>KNKGnf!j)fo4N}8Mwo;%2!CRx@qYA;dE`#EVU`5as zWZ?)Z4#&f6p6E?GMLr>2Fwkat&>AY2Mhj!C2(*N8Rik2y+>Od%)>j1xj1~a9UO?{A z^9myh)2r5KWV1M(p<)eMLe7~CtqS^86hOIakmw5tQJqkT0woTOS0Uo+QPd}e`pym6 z+ScAxsh}?`q6%%&BY>$ZwP*^^1C{ZpMST#o)L6{0M%N&~o$1^-ipCtwhvZp6GN$~j zLW1%r?oLZgGrXb~=2Ya&65sMk>& zbNGCDd!d-mWveVP^t@4tzRjKD%&?)HpDc!IwXxcI4Z?x@7VOfH(9|Mh96DlHpw0#k zp37*|lDb%h?#cN}Cro2xI8G&QV>8%Fg}wxGzee!zWQM(2-j=Z1KGdUy8hbY6Vw@Y_!53^f4LAr6-f)R|CI|J7aie)8c z3oLHn#D(%mv=V^|lcbC)J4t0$Jd->mZ^g6$Y8(1D-f(Nszj4#h>VbivZ|(Ir-wH^ZPgm3 zR~bvJzSVx3GvkzkfGL@Z!9pY4f!{1x_5M^lE`?Gvf(s6BLvW~l2h@{e=J4pp=xpUL3R8;0^?jY)%Z%gV!)*%(~=}s<=c>N<8!&SxZ3i<(yMWb?lRq2=8!K*w##HU=H=><(9CWHlZd3my^njag(qcb!a-ML{W$ef%x*Kh^e0Y z;Kl5iT0TXR$p%ToJZv*NEuI_STB~oxf*2dE+;5HcO|dPAbI`h3@ToKtUqET)#jO&Z z?m?MDuT5NwsdK&%5ka}Aq-`794^jLYX3VAw-=_0=)ID)=+d7RmucVN!Y`#r0Wn*b; zOes?~lXY?xB`YN*ywWxAQ#B=|vpXE!91?9xnhb$V>h89(JOCu0G7F3*%RFy+A(yJ6 h0=hO?Hf5M(U{j?aCE1l#H0)_+iH2Msb