#include "simul.h" /************************************************************************** Generic Cache simulation Simulates the execution timings of a cached memory block. The simulation is not exact. It only simulates the extra time needed to get the first word of a cache line and the time the bus is blocked by cache operations. The cache sizes and configurations can be configured from the command line. It supports harvard and unified cache structures. Only aligned memory accesses are supported. The "main" memory is the regular simulated memory. All writes also update immediatelly the simulated memory (so this implemention will not allow to debug cache coherency problems). The example can be used as a template to implement a specific cache simulation. Improvements could be - support of cache related control registers - support of cache flush / store etc. - support of timing effects of writeback buffers - more exact timing simulation (effects of word load order etc.) The configuration syntax is: SIM.LOAD gencache : D Data cache only (copyback) DW Data cache only (write-thru) I Instruction cache only H Harvard caches (copyback) HW Harvard caches (write-thru) U Unified caches (copyback) UW Unified caches (write-thru) ,: first and last address of cached memory : number of cache lines for instruction/data cache : number of cache blocks (sets) for instruction/data cache : size of one cache line (usually 16 or 32 bytes) : time in busclocks to get first word from burst : time in busclocks to get whole burst **************************************************************************/ #define GENCACHE_FLAG_VALID 1 #define GENCACHE_FLAG_DIRTY 2 simulTime MemoryBusCycleEndTime; /************************************************************************** Cache structures **************************************************************************/ typedef struct { int bustype; /* address covered by cached memory */ simulWord startaddress; simulWord endaddress; int lines; /* number of cache lines */ int blocks; /* number of cache blocks (sets) */ int linesize; /* size of a cacheline in bytes */ int writethru; /* writethru or copyback mode */ int clocks1; /* time till first word is accessible */ int clocks2; /* time till whole burst is complete */ simulWord mask; int lineshift; int linemask; simulWord * tags; /* tags array */ int * flags; /* flags array */ int * lru; /* lru info array */ unsigned char * data; /* data array */ int hits; /* statistic data */ int misses; } genCache; typedef struct { genCache icache; genCache dcache; } genCaches; /************************************************************************** Calculate parameters **************************************************************************/ static int GenCacheParameters(genCache * cacheptr) { int i, j; if (!cacheptr->linesize) return 0; cacheptr->mask = cacheptr->linesize - 1; if (cacheptr->linesize == 16) cacheptr->lineshift = 4; else if (cacheptr->linesize == 32) cacheptr->lineshift = 5; else if (cacheptr->linesize == 64) cacheptr->lineshift = 6; else return -1; cacheptr->linemask = cacheptr->lines - 1; for (j = 0, i = 1; i < cacheptr->lines; j++, i <<= 1); if (i != cacheptr->lines) return -1; return 0; } /************************************************************************** Flush cache contents **************************************************************************/ static void GenCacheFlushLine(genCache * cacheptr, int line) { int block; line *= cacheptr->blocks; for (block = 0; block < cacheptr->blocks; block++) { cacheptr->tags[line + block] = 0; cacheptr->flags[line + block] = 0; cacheptr->lru[line + block] = 0; } } static void GenCacheFlushAll(genCache * cacheptr) { int line; for (line = 0; line < cacheptr->lines; line++) { GenCacheFlushLine(cacheptr, line); } } /************************************************************************** Cache access primitives **************************************************************************/ static int GenCacheTest(genCache * cacheptr, int line, simulWord address) { int block; /* search all blocks for a cache hit */ for (block = 0; block < cacheptr->blocks; block++) { if (cacheptr->tags[line * cacheptr->blocks + block] == address && (cacheptr->flags[line * cacheptr->blocks + block] & GENCACHE_FLAG_VALID)) { return block; } } return -1; } static int GenCacheStore(simulProcessor processor, simulTime time, genCache * cacheptr, int line, simulWord address) { int i, block; unsigned char *pdata; simulTime rtime; simulWord data; block = GenCacheTest(cacheptr, line, address); if (block == -1) return -1; /* store data to main memory */ pdata = cacheptr->data + (line * cacheptr->blocks + block) * cacheptr->linesize; for (i = 0; i < cacheptr->linesize; i += 4) { SIMUL_LoadWord(processor, pdata, 32, &data); if (SIMUL_WriteMemory(processor, cacheptr->bustype, &address, 32, SIMUL_MEMORY_HIDDEN, &data) != SIMUL_MEMORY_OK) return -1; address += 4; pdata += 4; } SIMUL_GetClockCycle(processor, SIMUL_CLOCK_BUS, &rtime); MemoryBusCycleEndTime = time + rtime * cacheptr->clocks2; /* block the bus till * the operation is * complete */ cacheptr->flags[line * cacheptr->blocks + block] &= ~GENCACHE_FLAG_DIRTY; return 0; } static int GenCacheLoad(simulProcessor processor, simulTime time, genCache * cacheptr, int line, simulWord address) { int i, block, blocklru; int lastlru; unsigned char *pdata; simulTime rtime; simulWord data; /* wait till last memory operation is complete */ if (time < MemoryBusCycleEndTime) { SIMUL_Stall(processor, SIMUL_STALL_ABS, &MemoryBusCycleEndTime); time = MemoryBusCycleEndTime; } /* search oldest block or first unused block */ blocklru = 0; lastlru = 0; for (block = 0; block < cacheptr->blocks; block++) { if (!(cacheptr->flags[line * cacheptr->blocks + block] & GENCACHE_FLAG_VALID)) { if (lastlru != 0x7fffffff) { lastlru = 0x7fffffff; blocklru = block; /* found an unused block -> take it */ } } if (cacheptr->lru[line * cacheptr->blocks + block] > lastlru) { lastlru = cacheptr->lru[line * cacheptr->blocks + block]; blocklru = block; } cacheptr->lru[line * cacheptr->blocks + block]++; } /* store block if it still contains dirty data */ if (cacheptr->flags[line * cacheptr->blocks + blocklru] & GENCACHE_FLAG_DIRTY) { GenCacheStore(processor, time, cacheptr, line, cacheptr->tags[line * cacheptr->blocks + blocklru]); if (time < MemoryBusCycleEndTime) { SIMUL_Stall(processor, SIMUL_STALL_ABS, &MemoryBusCycleEndTime); time = MemoryBusCycleEndTime; } } /* set line flags/tags (only to have correct values for recursion) */ cacheptr->lru[line * cacheptr->blocks + blocklru] = 0; cacheptr->tags[line * cacheptr->blocks + blocklru] = address; cacheptr->flags[line * cacheptr->blocks + blocklru] = 0; /* load data from main memory */ pdata = cacheptr->data + (line * cacheptr->blocks + blocklru) * cacheptr->linesize; for (i = 0; i < cacheptr->linesize; i += 4) { if (SIMUL_ReadMemory(processor, cacheptr->bustype, &address, 32, SIMUL_MEMORY_HIDDEN, &data) != SIMUL_MEMORY_OK) return -1; SIMUL_SaveWord(processor, pdata, 32, &data); address += 4; pdata += 4; } /* set line tags */ cacheptr->flags[line * cacheptr->blocks + blocklru] = GENCACHE_FLAG_VALID; SIMUL_GetClockCycle(processor, SIMUL_CLOCK_BUS, &rtime); MemoryBusCycleEndTime = time + rtime * cacheptr->clocks2; /* block the bus till * the operation is * complete */ return blocklru; } /************************************************************************** Read and Write routines **************************************************************************/ static int SIMULAPI GenCacheRead(simulProcessor processor, simulCallbackStruct * cbs, simulPtr private) { int line, block, offset; simulWord address; genCache *cacheptr; genCaches *cacheptrs = (genCaches *) private; if (cbs->x.bus.cycletype & SIMUL_MEMORY_FETCH) cacheptr = &cacheptrs->icache; else cacheptr = &cacheptrs->dcache; line = (cbs->x.bus.address >> cacheptr->lineshift) & cacheptr->linemask; address = cbs->x.bus.address & ~cacheptr->mask; block = GenCacheTest(cacheptr, line, address); if (block == -1) { if (!cbs->x.bus.cycletype) return SIMUL_MEMORY_CONTINUE; /* read memory direct (debug * stopped or recursion) */ if (!cacheptr->lines) return SIMUL_MEMORY_CONTINUE; /* read memory direct */ block = GenCacheLoad(processor, cbs->time, cacheptr, line, address); cacheptr->misses++; if (block == -1) { return SIMUL_MEMORY_FAIL; } cbs->x.bus.clocks = cacheptr->clocks1; /* time to fetch the first * word */ } else { if (cbs->x.bus.cycletype) cacheptr->hits++; cbs->x.bus.clocks = 0; /* assume 0-cycle access on cache hit */ } offset = cbs->x.bus.address & cacheptr->mask; if (offset + (cbs->x.bus.width >> 3) > cacheptr->linesize) return SIMUL_MEMORY_FAIL; SIMUL_LoadWord(processor, cacheptr->data + (line * cacheptr->blocks + block) * cacheptr->linesize + offset, cbs->x.bus.width, &cbs->x.bus.data); return SIMUL_MEMORY_OK; } static int SIMULAPI GenCacheWrite(simulProcessor processor, simulCallbackStruct * cbs, simulPtr private) { int line, block, offset; simulWord address; genCache *cacheptr; genCaches *cacheptrs = (genCaches *) private; cacheptr = &cacheptrs->dcache; line = (cbs->x.bus.address >> cacheptr->lineshift) & cacheptr->linemask; address = cbs->x.bus.address & ~cacheptr->mask; block = GenCacheTest(cacheptr, line, address); if (block == -1) { if (!cbs->x.bus.cycletype) return SIMUL_MEMORY_CONTINUE; /* write memory direct */ if (cacheptr->writethru) return SIMUL_MEMORY_CONTINUE; /* write memory direct */ if (!cacheptr->lines) return SIMUL_MEMORY_CONTINUE; /* write memory direct */ block = GenCacheLoad(processor, cbs->time, cacheptr, line, address); cacheptr->misses++; if (block == -1) { return SIMUL_MEMORY_FAIL; } cbs->x.bus.clocks = cacheptr->clocks1; /* time to fetch the first * word */ } else { if (cbs->x.bus.cycletype) cacheptr->hits++; if (!cacheptr->writethru) cbs->x.bus.clocks = 0; /* assume 0-cycle access on cache hit */ } offset = cbs->x.bus.address & cacheptr->mask; if (offset + (cbs->x.bus.width >> 3) > cacheptr->linesize) return SIMUL_MEMORY_FAIL; SIMUL_SaveWord(processor, cacheptr->data + (line * cacheptr->blocks + block) * cacheptr->linesize + offset, cbs->x.bus.width, &cbs->x.bus.data); if (cacheptr->writethru || !cbs->x.bus.cycletype) return SIMUL_MEMORY_CONTINUE; /* write memory also directly */ cacheptr->flags[line * cacheptr->blocks + block] |= GENCACHE_FLAG_DIRTY; return SIMUL_MEMORY_OK; } static int SIMULAPI GenCacheReset(simulProcessor processor, simulCallbackStruct * cbs, simulPtr private) { genCaches *cacheptrs = (genCaches *) private; GenCacheFlushAll(&cacheptrs->icache); cacheptrs->icache.misses = 0; cacheptrs->icache.hits = 0; GenCacheFlushAll(&cacheptrs->dcache); cacheptrs->dcache.misses = 0; cacheptrs->dcache.hits = 0; MemoryBusCycleEndTime = 0; return SIMUL_RESET_OK; } static int SIMULAPI GenCacheGo(simulProcessor processor, simulCallbackStruct * cbs, simulPtr private) { genCaches *cacheptrs = (genCaches *) private; /* * flush the instruction cache before starting to refetch any set * software breakpoints */ if (cacheptrs->icache.data != cacheptrs->dcache.data) { /* only required for * harvard caches */ GenCacheFlushAll(&cacheptrs->icache); } MemoryBusCycleEndTime = 0; return SIMUL_GO_OK; } static void GenCacheDump(simulProcessor processor, genCache * cacheptr) { int i, j; int line, block; char *state; simulWord data[16]; SIMUL_Printf(processor, "_address_line__set_state__lru_data\n"); for (line = 0; line < cacheptr->lines; line++) { for (block = 0; block < cacheptr->blocks; block++) { if (cacheptr->flags[line * cacheptr->blocks + block] & GENCACHE_FLAG_DIRTY) state = "dirty"; else if (cacheptr->flags[line * cacheptr->blocks + block] & GENCACHE_FLAG_VALID) state = "valid"; else continue; for (j = 0, i = 0; i < cacheptr->linesize; j++, i += 4) { SIMUL_LoadWord(processor, cacheptr->data + (line * cacheptr->blocks + block) * cacheptr->linesize + i, 32, &data[j]); } SIMUL_Printf(processor, "%08x %4x %4x %s %4x %08x %08x %08x %08x\n", cacheptr->tags[line * cacheptr->blocks + block], line, block, state, cacheptr->lru[line * cacheptr->blocks + block], data[0], data[1], data[2], data[3]); } } } static void GenCacheStat(simulProcessor processor, genCache * cacheptr, char * name) { int ratio; if (cacheptr->hits + cacheptr->misses > 0) ratio = cacheptr->hits * 100 / (cacheptr->hits + cacheptr->misses); else ratio = 0; SIMUL_Printf(processor, "%s: hits: %d, misses: %d, ratio: %d%%\n", name, cacheptr->hits, cacheptr->misses, ratio); } static int SIMULAPI GenCacheCommand(simulProcessor processor, simulCallbackStruct * cbs, simulPtr private) { genCaches *cacheptrs = (genCaches *) private; if (!strcmp(cbs->x.command.argp[0], "ICFLUSH")) { GenCacheFlushAll(&cacheptrs->icache); return SIMUL_COMMAND_OK; } if (!strcmp(cbs->x.command.argp[0], "DCFLUSH")) { GenCacheFlushAll(&cacheptrs->dcache); return SIMUL_COMMAND_OK; } if (!strcmp(cbs->x.command.argp[0], "ICDUMP")) { GenCacheDump(processor, &cacheptrs->icache); return SIMUL_COMMAND_OK; } if (!strcmp(cbs->x.command.argp[0], "DCDUMP")) { GenCacheDump(processor, &cacheptrs->dcache); return SIMUL_COMMAND_OK; } if (!strcmp(cbs->x.command.argp[0], "STAT")) { GenCacheStat(processor, &cacheptrs->icache, "ICACHE"); GenCacheStat(processor, &cacheptrs->dcache, "DCACHE"); return SIMUL_COMMAND_OK; } return SIMUL_COMMAND_OK; } /************************************************************************** Entry point of the Model **************************************************************************/ int SIMULAPI SIMUL_Init(simulProcessor processor, simulCallbackStruct * cbs) { int ilines, iblocks, dlines, dblocks, linesize; int icache, dcache, unified, writethru; int clocks1, clocks2; genCaches *cacheptrs; strcpy(cbs->x.init.modelname, __DATE__ " Generic Cache"); if (cbs->x.init.argc != 11) { SIMUL_Warning(processor, "parameters: \"D\"|\"DW\"|\"I\"|\"H\"|\"HW\"|\"U\"|\"UW\" "); return SIMUL_INIT_FAIL; } if (cbs->x.init.argpbustype[2] != cbs->x.init.argpbustype[3]) { SIMUL_Warning(processor, "parameters: from and to must be on same bus"); return SIMUL_INIT_FAIL; } writethru = 0; unified = 0; if (!strcmp("D", cbs->x.init.argp[1])) { dcache = 1; icache = 0; } else if (!strcmp("\"DW\"", cbs->x.init.argp[1])) { dcache = 1; icache = 0; writethru = 1; } else if (!strcmp("\"I\"", cbs->x.init.argp[1])) { dcache = 0; icache = 1; } else if (!strcmp("\"H\"", cbs->x.init.argp[1])) { dcache = 1; icache = 1; } else if (!strcmp("\"HW\"", cbs->x.init.argp[1])) { dcache = 1; icache = 1; writethru = 1; } else if (!strcmp("\"U\"", cbs->x.init.argp[1])) { dcache = 1; icache = 1; unified = 1; } else if (!strcmp("\"UW\"", cbs->x.init.argp[1])) { dcache = 1; icache = 1; unified = 1; writethru = 1; } else { SIMUL_Warning(processor, "parameters: last parameter not valid"); return SIMUL_INIT_FAIL; } linesize = cbs->x.init.argpint[8]; clocks1 = cbs->x.init.argpint[9]; clocks2 = cbs->x.init.argpint[10]; cacheptrs = (genCaches *) SIMUL_Alloc(processor, sizeof(genCaches)); if (icache) { ilines = cbs->x.init.argpint[4]; iblocks = cbs->x.init.argpint[5]; cacheptrs->icache.bustype = cbs->x.init.argpbustype[2]; cacheptrs->icache.startaddress = cbs->x.init.argpaddress[2]; cacheptrs->icache.endaddress = cbs->x.init.argpaddress[3]; cacheptrs->icache.lines = ilines; cacheptrs->icache.blocks = iblocks; cacheptrs->icache.linesize = linesize; cacheptrs->icache.writethru = 0; cacheptrs->icache.clocks1 = clocks1; cacheptrs->icache.clocks2 = clocks2; cacheptrs->icache.data = (unsigned char *) SIMUL_Alloc(processor, ilines * iblocks * linesize); cacheptrs->icache.tags = (simulWord *) SIMUL_Alloc(processor, ilines * iblocks * sizeof(simulWord)); cacheptrs->icache.flags = (int *) SIMUL_Alloc(processor, ilines * iblocks * sizeof(int)); cacheptrs->icache.lru = (int *) SIMUL_Alloc(processor, ilines * iblocks * sizeof(int)); } if (dcache) { dlines = cbs->x.init.argpint[6]; dblocks = cbs->x.init.argpint[7]; cacheptrs->dcache.bustype = cbs->x.init.argpbustype[2]; cacheptrs->dcache.startaddress = cbs->x.init.argpaddress[2]; cacheptrs->dcache.endaddress = cbs->x.init.argpaddress[3]; cacheptrs->dcache.lines = dlines; cacheptrs->dcache.blocks = dblocks; cacheptrs->dcache.linesize = linesize; cacheptrs->dcache.writethru = writethru; cacheptrs->dcache.clocks1 = clocks1; cacheptrs->dcache.clocks2 = clocks2; if (unified) { if (dlines != ilines || dblocks != iblocks) { SIMUL_Warning(processor, "parameters: icache and dcache size not same"); return SIMUL_INIT_FAIL; } cacheptrs->dcache.data = cacheptrs->icache.data; cacheptrs->dcache.tags = cacheptrs->icache.tags; cacheptrs->dcache.flags = cacheptrs->icache.flags; cacheptrs->dcache.lru = cacheptrs->icache.lru; } else { cacheptrs->dcache.data = (unsigned char *) SIMUL_Alloc(processor, dlines * dblocks * linesize); cacheptrs->dcache.tags = (simulWord *) SIMUL_Alloc(processor, dlines * dblocks * sizeof(simulWord)); cacheptrs->dcache.flags = (int *) SIMUL_Alloc(processor, dlines * dblocks * sizeof(int)); cacheptrs->dcache.lru = (int *) SIMUL_Alloc(processor, dlines * dblocks * sizeof(int)); } } if (GenCacheParameters(&cacheptrs->icache) || GenCacheParameters(&cacheptrs->dcache)) { SIMUL_Warning(processor, "parameters: invalid line or blocksizes"); return SIMUL_INIT_FAIL; } GenCacheFlushAll(&cacheptrs->icache); GenCacheFlushAll(&cacheptrs->dcache); SIMUL_RegisterResetCallback(processor, GenCacheReset, (simulPtr) cacheptrs); SIMUL_RegisterGoCallback(processor, GenCacheGo, (simulPtr) cacheptrs); SIMUL_RegisterCommandCallback(processor, GenCacheCommand, (simulPtr) cacheptrs); SIMUL_RegisterBusReadCallback(processor, GenCacheRead, (simulPtr) cacheptrs, cbs->x.init.argpbustype[2], &cbs->x.init.argpaddress[2], &cbs->x.init.argpaddress[3]); SIMUL_RegisterBusWriteCallback(processor, GenCacheWrite, (simulPtr) cacheptrs, cbs->x.init.argpbustype[2], &cbs->x.init.argpaddress[2], &cbs->x.init.argpaddress[3]); return SIMUL_INIT_OK; }