diff --git a/.gitignore b/.gitignore index 6e9920603..630731b23 100644 --- a/.gitignore +++ b/.gitignore @@ -112,4 +112,6 @@ sim/results-error/ sim/test1.rep sim/vsim.log tests/coverage/*.elf -*.elf.memfile \ No newline at end of file +*.elf.memfile +sim/*Cache.log +sim/branch \ No newline at end of file diff --git a/bin/CacheSim.py b/bin/CacheSim.py new file mode 100755 index 000000000..5669b35c1 --- /dev/null +++ b/bin/CacheSim.py @@ -0,0 +1,241 @@ +#!/usr/bin/env python3 + +########################################### +## CacheSim.py +## +## Written: lserafini@hmc.edu +## Created: 27 March 2023 +## Modified: 5 April 2023 +## +## Purpose: Simulate a L1 D$ or I$ for comparison with Wally +## +## A component of the CORE-V-WALLY configurable RISC-V project. +## +## Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University +## +## SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +## +## Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file +## except in compliance with the License, or, at your option, the Apache License version 2.0. You +## may obtain a copy of the License at +## +## https:##solderpad.org/licenses/SHL-2.1/ +## +## Unless required by applicable law or agreed to in writing, any work distributed under the +## License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +## either express or implied. See the License for the specific language governing permissions +## and limitations under the License. +################################################################################################ + +# how to invoke this simulator: +# CacheSim.py -f (-v) +# so the default invocation for rv64gc is 'CacheSim.py 64 4 56 44 -f ' +# the log files to run this simulator on can be generated from testbench.sv +# by setting I_CACHE_ADDR_LOGGER and/or D_CACHE_ADDR_LOGGER to 1 before running tests. +# I (Lim) recommend logging a single set of tests (such as wally64priv) at a time. +# This helps avoid unexpected logger behavior. +# With verbose mode off, the simulator only reports mismatches between its and Wally's behavior. +# With verbose mode on, the simulator logs each access into the cache. + +import sys +import math +import argparse +import os + +class CacheLine: + def __init__(self): + self.tag = 0 + self.valid = False + self.dirty = False + + def __str__(self): + string = "(V: " + str(self.valid) + ", D: " + str(self.dirty) + string += ", Tag: " + str(hex(self.tag)) + ")" + return string + + def __repr__(self): + return self.__str__() + +class Cache: + def __init__(self, numsets, numways, addrlen, taglen): + self.numways = numways + self.numsets = numsets + + self.addrlen = addrlen + self.taglen = taglen + self.setlen = int(math.log(numsets, 2)) + self.offsetlen = self.addrlen - self.taglen - self.setlen + + self.ways = [] + for i in range(numways): + self.ways.append([]) + for j in range(numsets): + self.ways[i].append(CacheLine()) + + self.pLRU = [] + for i in range(self.numsets): + self.pLRU.append([0]*(self.numways-1)) + + # flushes the cache by setting all dirty bits to False + def flush(self): + for way in self.ways: + for line in way: + line.dirty = False + + # invalidates the cache by setting all valid bits to False + def invalidate(self): + for way in self.ways: + for line in way: + line.valid = False + + # resets the pLRU to a fresh 2-D array of 0s + def clear_pLRU(self): + self.pLRU = [] + for i in range(self.numsets): + self.pLRU.append([0]*(self.numways-1)) + + # splits the given address into tag, set, and offset + def splitaddr(self, addr): + # no need for offset in the sim, but it's here for debug + tag = addr >> (self.setlen + self.offsetlen) & int('1'*self.taglen, 2) + setnum = (addr >> self.offsetlen) & int('1'*self.setlen, 2) + offset = addr & int('1'*self.offsetlen, 2) + return tag, setnum, offset + + # performs a cache access with the given address. + # returns a character representing the outcome: + # H/M/E/D - hit, miss, eviction, or eviction with writeback + def cacheaccess(self, addr, write=False): + tag, setnum, _ = self.splitaddr(addr) + + # check our ways to see if we have a hit + for waynum in range(self.numways): + line = self.ways[waynum][setnum] + if line.tag == tag and line.valid: + line.dirty = line.dirty or write + self.update_pLRU(waynum, setnum) + return 'H' + + # we didn't hit, but we may not need to evict. + # check for an empty way line. + for waynum in range(self.numways): + line = self.ways[waynum][setnum] + if not line.valid: + line.tag = tag + line.valid = True + line.dirty = write + self.update_pLRU(waynum, setnum) + return 'M' + + # we need to evict. Select a victim and overwrite. + victim = self.getvictimway(setnum) + line = self.ways[victim][setnum] + prevdirty = line.dirty + line.tag = tag + line.valid = True # technically redundant + line.dirty = write + self.update_pLRU(victim, setnum) + return 'D' if prevdirty else 'E' + + # updates the psuedo-LRU tree for the given set + # with an access to the given way + def update_pLRU(self, waynum, setnum): + if self.numways == 1: + return + + tree = self.pLRU[setnum] + bottomrow = (self.numways - 1)//2 + index = (waynum // 2) + bottomrow + tree[index] = int(not (waynum % 2)) + while index > 0: + parent = (index-1) // 2 + tree[parent] = index % 2 + index = parent + + # uses the psuedo-LRU tree to select + # a victim way from the given set + # returns the victim way as an integer + def getvictimway(self, setnum): + if self.numways == 1: + return 0 + + tree = self.pLRU[setnum] + index = 0 + bottomrow = (self.numways - 1) // 2 #first index on the bottom row of the tree + while index < bottomrow: + if tree[index] == 0: + # Go to the left child + index = index*2 + 1 + else: #tree[index] == 1 + # Go to the right child + index = index*2 + 2 + + victim = (index - bottomrow)*2 + if tree[index] == 1: + victim += 1 + + return victim + + def __str__(self): + string = "" + for i in range(self.numways): + string += "Way " + str(i) + ": " + for line in self.ways[i]: + string += str(line) + ", " + string += "\n\n" + return string + + def __repr__(self): + return self.__str__() + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Simulates a L1 cache.") + parser.add_argument('numlines', type=int, help="The number of lines per way (a power of 2)", metavar="L") + parser.add_argument('numways', type=int, help="The number of ways (a power of 2)", metavar='W') + parser.add_argument('addrlen', type=int, help="Length of the address in bits (a power of 2)", metavar="A") + parser.add_argument('taglen', type=int, help="Length of the tag in bits", metavar="T") + parser.add_argument('-f', "--file", required=True, help="Log file to simulate from") + parser.add_argument('-v', "--verbose", action='store_true', help="verbose/full-trace mode") + + args = parser.parse_args() + cache = Cache(args.numlines, args.numways, args.addrlen, args.taglen) + #numtests = -1 + extfile = os.path.expanduser(args.file) + with open(extfile, "r") as f: + for ln in f: + ln = ln.strip() + lninfo = ln.split() + if len(lninfo) < 3: #non-address line + if len(lninfo) > 0 and (lninfo[0] == 'BEGIN' or lninfo[0] == 'TRAIN'): + # currently BEGIN and END traces aren't being recorded correctly + # trying TRAIN clears instead + cache.invalidate() # a new test is starting, so 'empty' the cache + cache.clear_pLRU() + #numtests +=1 + if args.verbose: + print("New Test") + + else: + if lninfo[1] == 'F': + cache.flush() + if args.verbose: + print("F") + elif lninfo[1] == 'I': + cache.invalidate() + if args.verbose: + print("I") + else: + addr = int(lninfo[0], 16) + iswrite = lninfo[1] == 'W' or lninfo[1] == 'A' + result = cache.cacheaccess(addr, iswrite) + if args.verbose: + tag, setnum, offset = cache.splitaddr(addr) + print(hex(addr), hex(tag), hex(setnum), hex(offset), lninfo[2], result) + if not result == lninfo[2]: + print("Result mismatch at address", lninfo[0], ". Wally:", lninfo[2],", Sim:", result) #, "in test", numtests) + + + + + diff --git a/testbench/testbench.sv b/testbench/testbench.sv index 87718b48b..adcb58d56 100644 --- a/testbench/testbench.sv +++ b/testbench/testbench.sv @@ -480,7 +480,7 @@ logic [3:0] dummy; assign EndSample = DCacheFlushStart & ~DCacheFlushDone; flop #(1) BeginReg(clk, StartSampleFirst, BeginDelayed); - assign Begin = StartSampleFirst & ~ BeginDelayed; + assign Begin = StartSampleFirst & ~BeginDelayed; end @@ -560,7 +560,11 @@ end string LogFile; logic resetD, resetEdge; logic Enable; - assign Enable = ~dut.core.StallD & ~dut.core.FlushD & dut.core.ifu.bus.icache.CacheRWF[1] & ~reset; + // assign Enable = ~dut.core.StallD & ~dut.core.FlushD & dut.core.ifu.bus.icache.CacheRWF[1] & ~reset; + + // this version of Enable allows for accurate eviction logging. + // Likely needs further improvement. + assign Enable = dut.core.ifu.bus.icache.icache.cachefsm.LRUWriteEn & ~reset; flop #(1) ResetDReg(clk, reset, resetD); assign resetEdge = ~reset & resetD; initial begin @@ -568,51 +572,58 @@ end file = $fopen(LogFile, "w"); $fwrite(file, "BEGIN %s\n", memfilename); end - string HitMissString; - assign HitMissString = dut.core.ifu.InvalidateICacheM ? "I" : - dut.core.ifu.bus.icache.icache.CacheHit ? "H" : "M"; + string AccessTypeString, HitMissString; + assign HitMissString = dut.core.ifu.bus.icache.icache.CacheHit ? "H" : + dut.core.ifu.bus.icache.icache.vict.cacheLRU.AllValid ? "E" : "M"; + assign AccessTypeString = dut.core.ifu.InvalidateICacheM ? "I" : "R"; always @(posedge clk) begin if(resetEdge) $fwrite(file, "TRAIN\n"); if(Begin) $fwrite(file, "BEGIN %s\n", memfilename); if(Enable) begin // only log i cache reads - $fwrite(file, "%h R %s\n", dut.core.ifu.PCPF, HitMissString); + $fwrite(file, "%h %s %s\n", dut.core.ifu.PCPF, AccessTypeString, HitMissString); end if(EndSample) $fwrite(file, "END %s\n", memfilename); end end - // old version + if (`DCACHE_SUPPORTED && `D_CACHE_ADDR_LOGGER) begin : DCacheLogger int file; string LogFile; logic resetD, resetEdge; logic Enabled; - string AccessTypeString, HitMissString, EvictString; + string AccessTypeString, HitMissString; flop #(1) ResetDReg(clk, reset, resetD); assign resetEdge = ~reset & resetD; - assign HitMissString = dut.core.lsu.bus.dcache.dcache.CacheHit ? "H" : "M"; + assign HitMissString = dut.core.lsu.bus.dcache.dcache.CacheHit ? "H" : + (!dut.core.lsu.bus.dcache.dcache.vict.cacheLRU.AllValid) ? "M" : + dut.core.lsu.bus.dcache.dcache.LineDirty ? "D" : "E"; assign AccessTypeString = dut.core.lsu.bus.dcache.FlushDCache ? "F" : dut.core.lsu.bus.dcache.CacheAtomicM[1] ? "A" : dut.core.lsu.bus.dcache.CacheRWM == 2'b10 ? "R" : dut.core.lsu.bus.dcache.CacheRWM == 2'b01 ? "W" : "NULL"; - assign EvictString = HitMissString == "H" ? "X" : - dut.core.lsu.bus.dcache.dcache.LineDirty ? "E" : "N"; - assign Enabled = (dut.core.lsu.bus.dcache.dcache.cachefsm.CurrState == 0) & + // assign Enabled = (dut.core.lsu.bus.dcache.dcache.cachefsm.CurrState == 0) & + // ~dut.core.lsu.bus.dcache.dcache.cachefsm.FlushStage & + // (AccessTypeString != "NULL"); + + // This version of enable allows for accurate eviction logging. + // Likely needs further improvement. + assign Enabled = dut.core.lsu.bus.dcache.dcache.cachefsm.LRUWriteEn & ~dut.core.lsu.bus.dcache.dcache.cachefsm.FlushStage & (AccessTypeString != "NULL"); initial begin - LogFile = $psprintf("DCache.log"); + LogFile = $psprintf("DCache.log"); file = $fopen(LogFile, "w"); - $fwrite(file, "BEGIN %s\n", memfilename); - end + $fwrite(file, "BEGIN %s\n", memfilename); + end always @(posedge clk) begin if(resetEdge) $fwrite(file, "TRAIN\n"); if(Begin) $fwrite(file, "BEGIN %s\n", memfilename); if(Enabled) begin - $fwrite(file, "%h %s %s %s\n", dut.core.lsu.PAdrM, AccessTypeString, HitMissString, EvictString); + $fwrite(file, "%h %s %s\n", dut.core.lsu.PAdrM, AccessTypeString, HitMissString); end if(EndSample) $fwrite(file, "END %s\n", memfilename); end diff --git a/tests/custom/cacheSimTest/CacheSimTest.py b/tests/custom/cacheSimTest/CacheSimTest.py new file mode 100755 index 000000000..44cc00b06 --- /dev/null +++ b/tests/custom/cacheSimTest/CacheSimTest.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 + +########################################### +## CacheSimTest.py +## +## Written: lserafini@hmc.edu +## Created: 4 April 2023 +## Modified: 5 April 2023 +## +## Purpose: Confirm that the cache simulator behaves as expected. +## +## A component of the CORE-V-WALLY configurable RISC-V project. +## +## Copyright (C) 2021-23 Harvey Mudd College & Oklahoma State University +## +## SPDX-License-Identifier: Apache-2.0 WITH SHL-2.1 +## +## Licensed under the Solderpad Hardware License v 2.1 (the “License”); you may not use this file +## except in compliance with the License, or, at your option, the Apache License version 2.0. You +## may obtain a copy of the License at +## +## https:##solderpad.org/licenses/SHL-2.1/ +## +## Unless required by applicable law or agreed to in writing, any work distributed under the +## License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +## either express or implied. See the License for the specific language governing permissions +## and limitations under the License. +################################################################################################ + +import sys +import os + +sys.path.append(os.path.expanduser("~/cvw/bin")) +import CacheSim as cs + +if __name__ == "__main__": + cache = cs.Cache(16, 4, 16, 8) + # 0xABCD -> tag: AB, set: C, offset: D + + #address split checking + assert (cache.splitaddr(0x1234) == (0x12,0x3,0x4)) + assert (cache.splitaddr(0x2638) == (0x26,0x3,0x8)) + assert (cache.splitaddr(0xA3E6) == (0xA3,0xE,0x6)) + + #insert way 0 set C tag AB + assert (cache.cacheaccess(0xABCD) == 'M') + assert (cache.ways[0][0xC].tag == 0xAB) + assert (cache.cacheaccess(0xABCD) == 'H') + assert (cache.pLRU[0xC] == [1,1,0]) + + #make way 0 set C dirty + assert (cache.cacheaccess(0xABCD, True) == 'H') + + #insert way 1 set C tag AC + assert (cache.cacheaccess(0xACCD) == 'M') + assert (cache.ways[1][0xC].tag == 0xAC) + assert (cache.pLRU[0xC] == [1,0,0]) + + #insert way 2 set C tag AD + assert (cache.cacheaccess(0xADCD) == 'M') + assert (cache.ways[2][0xC].tag == 0xAD) + assert (cache.pLRU[0xC] == [0,0,1]) + + #insert way 3 set C tag AE + assert (cache.cacheaccess(0xAECD) == 'M') + assert (cache.ways[3][0xC].tag == 0xAE) + assert (cache.pLRU[0xC] == [0,0,0]) + + #misc hit and pLRU checking + assert (cache.cacheaccess(0xABCD) == 'H') + assert (cache.pLRU[0xC] == [1,1,0]) + assert (cache.cacheaccess(0xADCD) == 'H') + assert (cache.pLRU[0xC] == [0,1,1]) + + #evict way 1, now set C has tag AF + assert (cache.cacheaccess(0xAFCD) == 'E') + assert (cache.ways[1][0xC].tag == 0xAF) + assert (cache.pLRU[0xC] == [1,0,1]) + + #evict way 3, now set C has tag AC + assert (cache.cacheaccess(0xACCD) == 'E') + assert (cache.ways[3][0xC].tag == 0xAC) + assert (cache.pLRU[0xC] == [0,0,0]) + + #evict way 0, now set C has tag EA + #this line was dirty, so there was a wb + assert (cache.cacheaccess(0xEAC2) == 'D') + assert (cache.ways[0][0xC].tag == 0xEA) + assert (cache.pLRU[0xC] == [1,1,0]) \ No newline at end of file