diff --git a/configs/ruby/GPU_VIPER.py b/configs/ruby/GPU_VIPER.py index 56d01b6d52e..ede7812b1b1 100644 --- a/configs/ruby/GPU_VIPER.py +++ b/configs/ruby/GPU_VIPER.py @@ -38,7 +38,7 @@ import m5 from m5.defines import buildEnv from m5.objects import * -from m5.util import addToPath +from m5.util import addToPath, warn from .Ruby import ( create_topology, @@ -98,7 +98,7 @@ class L2Cache(RubyCache): def create(self, size, assoc, options): self.size = MemorySize(size) self.assoc = assoc - self.replacement_policy = TreePLRURP() + self.replacement_policy = BRRIPRP() class CPCntrl(GPU_VIPER_CorePair_Controller, CntrlBase): @@ -336,18 +336,23 @@ class L3Cache(RubyCache): dataArrayBanks = 16 tagArrayBanks = 16 - def create(self, options, ruby_system, system): + def create(self, options, ruby_system, system, num_dirs=None): + # num_dirs: number of directory controllers sharing this L3 pool. + # Defaults to options.num_dirs (CPU-side) if not specified. + if num_dirs is None: + num_dirs = options.num_dirs self.size = MemorySize(options.l3_size) - self.size.value /= options.num_dirs + self.size.value /= num_dirs self.assoc = options.l3_assoc - self.dataArrayBanks /= options.num_dirs - self.tagArrayBanks /= options.num_dirs - self.dataArrayBanks /= options.num_dirs - self.tagArrayBanks /= options.num_dirs + # Each directory controller owns one L3 slice. Configure the internal + # data and tag bank count of that slice independently of the number of + # directory controllers. + self.dataArrayBanks = options.l3_num_banks + self.tagArrayBanks = options.l3_num_banks self.dataAccessLatency = options.l3_data_latency self.tagAccessLatency = options.l3_tag_latency self.resourceStalls = False - self.replacement_policy = TreePLRURP() + self.replacement_policy = BRRIPRP() class L3Cntrl(GPU_VIPER_L3Cache_Controller, CntrlBase): @@ -382,7 +387,7 @@ def connectWireBuffers( class DirCntrl(GPU_VIPER_Directory_Controller, CntrlBase): - def create(self, options, dir_ranges, ruby_system, system): + def create(self, options, dir_ranges, ruby_system, system, num_dirs=None): self.version = self.versionCount() self.response_latency = 30 @@ -393,7 +398,8 @@ def create(self, options, dir_ranges, ruby_system, system): ) self.L3CacheMemory = L3Cache() - self.L3CacheMemory.create(options, ruby_system, system) + self.L3CacheMemory.create(options, ruby_system, system, + num_dirs=num_dirs) self.l3_hit_latency = max( self.L3CacheMemory.dataAccessLatency, @@ -429,6 +435,12 @@ def define_options(parser): parser.add_argument("--tcp-issue-latency", type=int, default=1) parser.add_argument("--l3-data-latency", type=int, default=20) parser.add_argument("--l3-tag-latency", type=int, default=15) + parser.add_argument( + "--l3-num-banks", + type=int, + default=16, + help="Number of data and tag banks in each directory L3 slice", + ) parser.add_argument("--cpu-to-dir-latency", type=int, default=120) parser.add_argument("--gpu-to-dir-latency", type=int, default=120) parser.add_argument( @@ -438,6 +450,13 @@ def define_options(parser): "--no-tcc-resource-stalls", action="store_false", default=True ) parser.add_argument("--use-L3-on-WT", action="store_true", default=False) + parser.add_argument("--use-gpu-l3", action="store_true", default=False, + help="Enable L3 (Infinity Cache) fills for GPU " + "directory controllers") + parser.add_argument("--l3-exclusive", action="store_true", default=False, + help="Experimental non-CDNA3 victim-cache policy: " + "GPU reads consume L3 entries and fills occur " + "from lower-level evictions") parser.add_argument("--num-tbes", type=int, default=256) parser.add_argument("--l2-latency", type=int, default=50) # load to use parser.add_argument( @@ -560,14 +579,14 @@ def construct_dirs(options, system, ruby_system, network): # For an odd number of CPUs, still create the right number of controllers TCC_bits = int(math.log(options.num_tccs, 2)) + dir_bits = int(math.log(options.num_dirs, 2)) + block_size_bits = int(math.log(options.cacheline_size, 2)) if options.numa_high_bit: numa_bit = options.numa_high_bit else: # if the numa_bit is not specified, set the directory bits as the # lowest bits above the block offset bits, and the numa_bit as the # highest of those directory bits - dir_bits = int(math.log(options.num_dirs, 2)) - block_size_bits = int(math.log(options.cacheline_size, 2)) numa_bit = block_size_bits + dir_bits - 1 for i in range(options.num_dirs): @@ -584,6 +603,7 @@ def construct_dirs(options, system, ruby_system, network): dir_cntrl = DirCntrl(noTCCdir=True, TCC_select_num_bits=TCC_bits) dir_cntrl.create(options, dir_ranges, ruby_system, system) + dir_cntrl.L3CacheMemory.start_index_bit = block_size_bits + dir_bits dir_cntrl.number_of_TBEs = options.num_tbes dir_cntrl.useL3OnWT = options.use_L3_on_WT dir_cntrl.L2isWB = options.WB_L2 @@ -626,6 +646,9 @@ def construct_gpudirs(options, system, ruby_system, network): dir_cntrl_nodes = [] mem_ctrls = [] + if options.use_gpu_l3: + warn("GPU L3 write-back is not supported; modeling a write-through L3.") + xor_low_bit = 0 # For an odd number of CPUs, still create the right number of controllers @@ -651,9 +674,13 @@ def construct_gpudirs(options, system, ruby_system, network): TCC_select_num_bits=TCC_bits, clk_domain=system.fabric_clk, ) - dir_cntrl.create(options, [addr_range], ruby_system, system) + dir_cntrl.create(options, [addr_range], ruby_system, system, + num_dirs=options.dgpu_num_dirs) + dir_cntrl.L3CacheMemory.start_index_bit = block_size_bits + dir_bits dir_cntrl.number_of_TBEs = options.num_tbes - dir_cntrl.useL3OnWT = False + dir_cntrl.GPUonly = True + dir_cntrl.useL3OnWT = options.use_gpu_l3 + dir_cntrl.L3Exclusive = options.l3_exclusive dir_cntrl.L2isWB = options.WB_L2 # Connect the Directory controller to the ruby network diff --git a/src/mem/ruby/protocol/MOESI_AMD_Base-dir.sm b/src/mem/ruby/protocol/MOESI_AMD_Base-dir.sm index 2bdcc9624d7..ed39f1f4d62 100644 --- a/src/mem/ruby/protocol/MOESI_AMD_Base-dir.sm +++ b/src/mem/ruby/protocol/MOESI_AMD_Base-dir.sm @@ -39,6 +39,7 @@ machine(MachineType:Directory, "AMD Baseline protocol") bool GPUonly := "False"; int TCC_select_num_bits; bool useL3OnWT := "False"; + bool L3Exclusive := "False"; bool L2isWB; Cycles to_memory_controller_latency := 1; @@ -643,7 +644,14 @@ machine(MachineType:Directory, "AMD Baseline protocol") tbe.L3Hit := true; tbe.MemData := true; - L3CacheMemory.deallocate(address); + if (GPUonly && !L3Exclusive) { + // CDNA 3 Infinity Cache is a non-destructive memory-side cache, + // including for DMA/I/O traffic. + L3CacheMemory.setMRU(address); + } else { + // CPU-side behavior or experimental GPU exclusive policy. + L3CacheMemory.deallocate(address); + } } else { enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { out_msg.addr := address; @@ -670,16 +678,22 @@ machine(MachineType:Directory, "AMD Baseline protocol") tbe.LastSender := entry.LastSender; tbe.L3Hit := true; tbe.MemData := true; - L3CacheMemory.deallocate(address); + if (GPUonly && !L3Exclusive) { + // CDNA 3 memory-side cache: retain the clean memory copy. + L3CacheMemory.setMRU(address); + } else { + // CPU-side behavior or experimental GPU exclusive policy. + L3CacheMemory.deallocate(address); + } } else { enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { out_msg.addr := address; - out_msg.Type := MemoryRequestType:MEMORY_READ; - out_msg.Sender := machineID; - out_msg.MessageSize := MessageSizeType:Request_Control; - } - } - } + out_msg.Type := MemoryRequestType:MEMORY_READ; + out_msg.Sender := machineID; + out_msg.MessageSize := MessageSizeType:Request_Control; + } + } + } } //This action profiles a hit or miss for a given request or write back. @@ -984,12 +998,17 @@ machine(MachineType:Directory, "AMD Baseline protocol") action(d_writeDataToMemory, "d", desc="Write data to memory") { peek(requestNetwork_in, CPURequestMsg) { - enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { - out_msg.addr := address; - out_msg.Type := MemoryRequestType:MEMORY_WB; - out_msg.Sender := machineID; - out_msg.MessageSize := MessageSizeType:Writeback_Data; - out_msg.DataBlk := in_msg.DataBlk; + // GPU: only write dirty data to memory. Clean evictions are no-ops + // since memory already has the data. + if (!GPUonly || in_msg.Type == CoherenceRequestType:VicDirty || + in_msg.Dirty) { + enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { + out_msg.addr := address; + out_msg.Type := MemoryRequestType:MEMORY_WB; + out_msg.Sender := machineID; + out_msg.MessageSize := MessageSizeType:Writeback_Data; + out_msg.DataBlk := in_msg.DataBlk; + } } if (tbe.Dirty == false) { // have to update the TBE, too, because of how this @@ -1094,7 +1113,10 @@ machine(MachineType:Directory, "AMD Baseline protocol") } action(wd_writeBackData, "wd", desc="Write back data if needed") { - if (tbe.wtData || tbe.atomicData || tbe.Dirty == false) { + // Write memory for data that must be persisted: + // WT/atomic updates, or dirty data returned by probes. + // Clean read data does not need writeback — memory already has it. + if (tbe.wtData || tbe.atomicData || tbe.Dirty) { // Only perform atomics in the directory if the SLC bit is set, or // if the L2 is WT if (tbe.atomicData && (tbe.isSLCSet || !L2isWB)) { @@ -1205,6 +1227,9 @@ machine(MachineType:Directory, "AMD Baseline protocol") } action(al_allocateL3Block, "al", desc="allocate the L3 block on WB") { + // GPU memory-side policy: write dirty data through to memory, then retain a + // clean copy in L3. Experimental exclusive policy uses victim-cache fills. + // CPU: original behavior — always fill. peek(requestNetwork_in, CPURequestMsg) { if (L3CacheMemory.isTagPresent(address)) { CacheEntry entry := static_cast(CacheEntry, "pointer", L3CacheMemory.lookup(address)); @@ -1219,14 +1244,19 @@ machine(MachineType:Directory, "AMD Baseline protocol") } else { if (L3CacheMemory.cacheAvail(address) == false) { Addr victim := L3CacheMemory.cacheProbe(address); - CacheEntry victim_entry := static_cast(CacheEntry, "pointer", - L3CacheMemory.lookup(victim)); - enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { - out_msg.addr := victim; - out_msg.Type := MemoryRequestType:MEMORY_WB; - out_msg.Sender := machineID; - out_msg.MessageSize := MessageSizeType:Writeback_Data; - out_msg.DataBlk := victim_entry.DataBlk; + if (GPUonly) { + // GPU L3 (Infinity Cache) is read-only — evicted entries are + // always clean, so silent drop is correct. + } else { + CacheEntry victim_entry := static_cast(CacheEntry, "pointer", + L3CacheMemory.lookup(victim)); + enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { + out_msg.addr := victim; + out_msg.Type := MemoryRequestType:MEMORY_WB; + out_msg.Sender := machineID; + out_msg.MessageSize := MessageSizeType:Writeback_Data; + out_msg.DataBlk := victim_entry.DataBlk; + } } L3CacheMemory.deallocate(victim); } @@ -1241,7 +1271,16 @@ machine(MachineType:Directory, "AMD Baseline protocol") } action(alwt_allocateL3BlockOnWT, "alwt", desc="allocate the L3 block on WT") { - if ((tbe.wtData || tbe.atomicData) && useL3OnWT) { + // GPU dirs: fill L3 on all completions (reads + writes) when enabled. + // In memory-side mode: skip refill on clean GPU read L3 hits (already kept by + // setMRU). If a probe supplied newer dirty data, refresh the L3 entry. + // Still fill on an L3 miss (new data from HBM) and WT/atomic L3 hit. + // In experimental exclusive mode, do not fill on read completion; lower-level + // evictions populate L3 through al_allocateL3Block instead. + // CPU dirs: original behavior — only fill on WT/atomic data. + if ((GPUonly && useL3OnWT && !L3Exclusive && + !(tbe.L3Hit && !tbe.Dirty && !tbe.wtData && !tbe.atomicData)) || + (!GPUonly && (tbe.wtData || tbe.atomicData) && useL3OnWT)) { //This tag check does not need to be counted as a hit or Miss, it has already been recorded. if (L3CacheMemory.isTagPresent(address)) { CacheEntry entry := static_cast(CacheEntry, "pointer", L3CacheMemory.lookup(address)); @@ -1251,14 +1290,19 @@ machine(MachineType:Directory, "AMD Baseline protocol") } else { if (L3CacheMemory.cacheAvail(address) == false) { Addr victim := L3CacheMemory.cacheProbe(address); - CacheEntry victim_entry := static_cast(CacheEntry, "pointer", - L3CacheMemory.lookup(victim)); - enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { - out_msg.addr := victim; - out_msg.Type := MemoryRequestType:MEMORY_WB; - out_msg.Sender := machineID; - out_msg.MessageSize := MessageSizeType:Writeback_Data; - out_msg.DataBlk := victim_entry.DataBlk; + if (GPUonly) { + // GPU L3 (Infinity Cache) is read-only — evicted entries are + // always clean, so silent drop is correct. + } else { + CacheEntry victim_entry := static_cast(CacheEntry, "pointer", + L3CacheMemory.lookup(victim)); + enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { + out_msg.addr := victim; + out_msg.Type := MemoryRequestType:MEMORY_WB; + out_msg.Sender := machineID; + out_msg.MessageSize := MessageSizeType:Writeback_Data; + out_msg.DataBlk := victim_entry.DataBlk; + } } L3CacheMemory.deallocate(victim); } @@ -1539,23 +1583,42 @@ machine(MachineType:Directory, "AMD Baseline protocol") ptl_popTriggerQueue; } - transition(BDR_M, MemData, U) { + transition(BDR_M, MemData, U) {L3TagArrayWrite, L3DataArrayWrite} { mt_writeMemDataToTBE; dd_sendResponseDmaData; + alwt_allocateL3BlockOnWT; wada_wakeUpAllDependentsAddr; dt_deallocateTBE; pm_popMemQueue; } - transition(BDW_M, MemData, U) { + transition(BDR_M, L3Hit, U) {L3TagArrayWrite, L3DataArrayWrite} { + dd_sendResponseDmaData; + alwt_allocateL3BlockOnWT; + wada_wakeUpAllDependentsAddr; + dt_deallocateTBE; + ptl_popTriggerQueue; + } + + transition(BDW_M, MemData, U) {L3TagArrayWrite, L3DataArrayWrite} { mt_writeMemDataToTBE; wd_writeBackData; da_sendResponseDmaAck; + alwt_allocateL3BlockOnWT; wada_wakeUpAllDependentsAddr; dt_deallocateTBE; pm_popMemQueue; } + transition(BDW_M, L3Hit, U) {L3DataArrayWrite, L3TagArrayWrite} { + wd_writeBackData; + da_sendResponseDmaAck; + alwt_allocateL3BlockOnWT; + wada_wakeUpAllDependentsAddr; + dt_deallocateTBE; + ptl_popTriggerQueue; + } + transition(BS_M, MemData, B){L3TagArrayWrite, L3DataArrayWrite} { mt_writeMemDataToTBE; wd_writeBackData; @@ -1666,16 +1729,26 @@ machine(MachineType:Directory, "AMD Baseline protocol") pm_popMemQueue; } - transition(BL2_M, MemData, U) { + transition(BL2_M, MemData, U) {L3TagArrayWrite, L3DataArrayWrite} { // Got the memory we were waiting for. We can unblock now. mt_writeMemDataToTBE; dd_sendResponseDmaData; + alwt_allocateL3BlockOnWT; wada_wakeUpAllDependentsAddr; pd_popDmaRequestQueue; dt_deallocateTBE; pm_popMemQueue; } + transition(BL2_M, L3Hit, U) {L3TagArrayWrite, L3DataArrayWrite} { + dd_sendResponseDmaData; + alwt_allocateL3BlockOnWT; + wada_wakeUpAllDependentsAddr; + pd_popDmaRequestQueue; + dt_deallocateTBE; + ptl_popTriggerQueue; + } + transition(BDR_PM, ProbeAcksComplete, BDR_M) { pt_popTriggerQueue; } @@ -1757,6 +1830,7 @@ machine(MachineType:Directory, "AMD Baseline protocol") transition(U, Flush, F) {L3TagArrayRead, L3TagArrayWrite} { t_allocateTBE; f_writeFlushDataToMemory; + alwt_allocateL3BlockOnWT; w_sendResponseWBAck; p_popRequestQueue; }