| /* | |
| * Copyright (c) 2021-2022 ARM Limited | |
| * All rights reserved | |
| * | |
| * The license below extends only to copyright in the software and shall | |
| * not be construed as granting a license to any other intellectual | |
| * property including but not limited to intellectual property relating | |
| * to a hardware implementation of the functionality of the software | |
| * licensed hereunder. You may use the software subject to the license | |
| * terms below provided that you ensure that this notice is replicated | |
| * unmodified and in its entirety in all distributions of the software, | |
| * modified or unmodified, in source code or in binary form. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions are | |
| * met: redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer; | |
| * redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution; | |
| * neither the name of the copyright holders nor the names of its | |
| * contributors may be used to endorse or promote products derived from | |
| * this software without specific prior written permission. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| //////////////////////////////////////////////////////////////////////////// | |
| // CHI-cache actions definitions | |
| //////////////////////////////////////////////////////////////////////////// | |
| action(AllocateTBE_Request, desc="") { | |
| if (storTBEs.areNSlotsAvailable(1)) { | |
| // reserve a slot for this request | |
| storTBEs.incrementReserved(); | |
| // Move request to rdy queue | |
| peek(reqInPort, CHIRequestMsg) { | |
| enqueue(reqRdyOutPort, CHIRequestMsg, allocation_latency) { | |
| assert(in_msg.addr == address); | |
| assert(in_msg.is_local_pf == false); | |
| out_msg := in_msg; | |
| } | |
| } | |
| } else { | |
| // we don't have resources to track this request; enqueue a retry | |
| peek(reqInPort, CHIRequestMsg) { | |
| assert(in_msg.allowRetry); | |
| enqueue(retryTriggerOutPort, RetryTriggerMsg, 0) { | |
| out_msg.addr := in_msg.addr; | |
| out_msg.usesTxnId := false; | |
| out_msg.event := Event:SendRetryAck; | |
| out_msg.retryDest := in_msg.requestor; | |
| retryQueue.emplace(in_msg.addr,false,in_msg.requestor); | |
| } | |
| } | |
| } | |
| reqInPort.dequeue(clockEdge()); | |
| } | |
| action(AllocateTBE_Request_WithCredit, desc="") { | |
| // TBE slot already reserved | |
| // Move request to rdy queue | |
| peek(reqInPort, CHIRequestMsg) { | |
| assert(in_msg.allowRetry == false); | |
| enqueue(reqRdyOutPort, CHIRequestMsg, allocation_latency) { | |
| assert(in_msg.addr == address); | |
| out_msg := in_msg; | |
| } | |
| } | |
| reqInPort.dequeue(clockEdge()); | |
| } | |
| action(AllocateTBE_Snoop, desc="") { | |
| // No retry for snoop requests; just create resource stall | |
| check_allocate(storSnpTBEs); | |
| storSnpTBEs.incrementReserved(); | |
| // Move request to rdy queue | |
| peek(snpInPort, CHIRequestMsg) { | |
| enqueue(snpRdyOutPort, CHIRequestMsg, allocation_latency) { | |
| assert(in_msg.addr == address); | |
| out_msg := in_msg; | |
| } | |
| // also remove snoop source from waiting retry destinations to prevent | |
| // deadlocks in which this snoop is blocked by a transaction that needs to | |
| // send a request to the snoop destination before going to BUSY_INTR, | |
| // but the destination needs the snoop to complete before sending retry | |
| // credit | |
| destsWaitingRetry.remove(in_msg.requestor); | |
| } | |
| snpInPort.dequeue(clockEdge()); | |
| } | |
| action(AllocateTBE_DvmSnoop, desc="") { | |
| // No retry for snoop requests; just create resource stall | |
| check_allocate(storDvmSnpTBEs); | |
| storDvmSnpTBEs.incrementReserved(); | |
| // Move request to rdy queue | |
| peek(snpInPort, CHIRequestMsg) { | |
| enqueue(snpRdyOutPort, CHIRequestMsg, allocation_latency) { | |
| assert(in_msg.usesTxnId); | |
| assert(in_msg.addr == address); | |
| out_msg := in_msg; | |
| } | |
| } | |
| snpInPort.dequeue(clockEdge()); | |
| } | |
| action(AllocateTBE_SeqRequest, desc="") { | |
| // No retry for sequencer requests; just create resource stall | |
| check_allocate(storTBEs); | |
| // reserve a slot for this request | |
| storTBEs.incrementReserved(); | |
| // Move request to rdy queue | |
| peek(seqInPort, RubyRequest) { | |
| enqueue(reqRdyOutPort, CHIRequestMsg, allocation_latency) { | |
| out_msg.addr := in_msg.LineAddress; | |
| assert((in_msg.Size > 0) && (in_msg.Size <= blockSize)); | |
| out_msg.accAddr := in_msg.PhysicalAddress; | |
| out_msg.accSize := in_msg.Size; | |
| out_msg.requestor := machineID; | |
| out_msg.fwdRequestor := machineID; | |
| out_msg.seqReq := in_msg.getRequestPtr(); | |
| out_msg.isSeqReqValid := true; | |
| assert(in_msg.Prefetch == PrefetchBit:No); | |
| out_msg.is_local_pf := false; | |
| out_msg.is_remote_pf := false; | |
| if ((in_msg.Type == RubyRequestType:LD) || | |
| (in_msg.Type == RubyRequestType:IFETCH)) { | |
| out_msg.type := CHIRequestType:Load; | |
| } else if (in_msg.Type == RubyRequestType:ST) { | |
| if (in_msg.Size == blockSize) { | |
| out_msg.type := CHIRequestType:StoreLine; | |
| } else { | |
| out_msg.type := CHIRequestType:Store; | |
| } | |
| } else { | |
| error("Invalid RubyRequestType"); | |
| } | |
| } | |
| } | |
| seqInPort.dequeue(clockEdge()); | |
| } | |
| action(AllocateTBE_SeqDvmRequest, desc="") { | |
| // No retry for sequencer requests; just create resource stall | |
| check_allocate(storDvmTBEs); | |
| // reserve a slot for this request | |
| storDvmTBEs.incrementReserved(); | |
| // Move request to rdy queue | |
| peek(seqInPort, RubyRequest) { | |
| enqueue(reqRdyOutPort, CHIRequestMsg, allocation_latency) { | |
| // DVM operations do not relate to memory addresses | |
| // Use the DVM transaction ID instead | |
| out_msg.usesTxnId := true; | |
| out_msg.txnId := in_msg.tlbiTransactionUid; | |
| // TODO - zero these out? | |
| out_msg.addr := in_msg.tlbiTransactionUid; | |
| out_msg.accAddr := in_msg.tlbiTransactionUid; | |
| out_msg.accSize := blockSize; | |
| assert(in_msg.Prefetch == PrefetchBit:No); | |
| out_msg.is_local_pf := false; | |
| out_msg.is_remote_pf := false; | |
| out_msg.requestor := machineID; | |
| out_msg.fwdRequestor := machineID; | |
| out_msg.seqReq := in_msg.getRequestPtr(); | |
| out_msg.isSeqReqValid := true; | |
| if (in_msg.Type == RubyRequestType:TLBI) { | |
| out_msg.type := CHIRequestType:DvmTlbi_Initiate; | |
| } else if (in_msg.Type == RubyRequestType:TLBI_SYNC) { | |
| out_msg.type := CHIRequestType:DvmSync_Initiate; | |
| } else if (in_msg.Type == RubyRequestType:TLBI_EXT_SYNC_COMP) { | |
| out_msg.type := CHIRequestType:DvmSync_ExternCompleted; | |
| } else { | |
| error("Invalid RubyRequestType"); | |
| } | |
| } | |
| } | |
| seqInPort.dequeue(clockEdge()); | |
| } | |
| action(AllocateTBE_PfRequest, desc="Allocate TBE for prefetch request") { | |
| // No retry for prefetch requests; just create resource stall | |
| check_allocate(storTBEs); | |
| // reserve a slot for this request | |
| storTBEs.incrementReserved(); | |
| // Move request to rdy queue | |
| peek(pfInPort, RubyRequest) { | |
| enqueue(reqRdyOutPort, CHIRequestMsg, 0) { | |
| out_msg.addr := in_msg.LineAddress; | |
| assert((in_msg.Size > 0) && (in_msg.Size <= blockSize)); | |
| out_msg.accAddr := in_msg.PhysicalAddress; | |
| out_msg.accSize := in_msg.Size; | |
| out_msg.requestor := machineID; | |
| out_msg.fwdRequestor := machineID; | |
| out_msg.seqReq := in_msg.getRequestPtr(); | |
| out_msg.isSeqReqValid := true; | |
| assert(in_msg.Prefetch != PrefetchBit:No); | |
| out_msg.is_local_pf := true; | |
| out_msg.is_remote_pf := false; | |
| if (in_msg.Type == RubyRequestType:LD) { | |
| out_msg.type := CHIRequestType:Load; | |
| } else if (in_msg.Type == RubyRequestType:ST) { | |
| error("CHI is not supporting prefetch store requests"); | |
| } else { | |
| error("Invalid RubyRequestType"); | |
| } | |
| } | |
| } | |
| pfInPort.dequeue(clockEdge()); | |
| } | |
| action(Initiate_Request, desc="") { | |
| State initial := getState(tbe, cache_entry, address); | |
| bool was_retried := false; | |
| peek(reqRdyPort, CHIRequestMsg) { | |
| set_tbe(allocateRequestTBE(address, in_msg)); | |
| // only a msg that was already retried doesn't allow a retry | |
| was_retried := in_msg.allowRetry == false; | |
| } | |
| DirEntry dir_entry := getDirEntry(address); | |
| copyCacheAndDir(cache_entry, dir_entry, tbe, initial); | |
| tbe.use_DMT := is_HN && enable_DMT; | |
| tbe.use_DCT := enable_DCT; | |
| bool alloc_entry := needCacheEntry(tbe.reqType, | |
| cache_entry, dir_entry, | |
| tbe.is_local_pf); | |
| bool dealloc_entry := needDeallocCacheEntry(tbe.reqType); | |
| assert((alloc_entry && dealloc_entry) == false); | |
| // always drops any data when not caching it or when this transaction | |
| // requires deallocation | |
| tbe.dataToBeInvalid := dealloc_entry || | |
| (is_invalid(cache_entry) && (alloc_entry == false)); | |
| tbe.doCacheFill := alloc_entry || is_valid(cache_entry); | |
| // model the initial tag array read | |
| tbe.actions.pushNB(Event:TagArrayRead); | |
| incomingTransactionStart(address, curTransitionEvent(), initial, was_retried); | |
| } | |
| action(Initiate_Request_DVM, desc="") { | |
| peek(reqRdyPort, CHIRequestMsg) { | |
| // "address" for DVM = transaction ID | |
| TBE tbe := allocateDvmRequestTBE(address, in_msg); | |
| set_tbe(tbe); | |
| } | |
| } | |
| action(Initiate_Request_Stale, desc="") { | |
| State initial := getState(tbe, cache_entry, address); | |
| bool was_retried := false; | |
| peek(reqRdyPort, CHIRequestMsg) { | |
| set_tbe(allocateRequestTBE(address, in_msg)); | |
| was_retried := in_msg.allowRetry == false; | |
| } | |
| copyCacheAndDir(cache_entry, getDirEntry(address), tbe, initial); | |
| // usually we consider data locally invalid on RU states even if we | |
| // have a copy; so it override it to valid so we can comeback to UD_RU/UC_RU | |
| // at the end of this transaction | |
| if (tbe.dir_ownerExists && tbe.dir_ownerIsExcl && is_valid(cache_entry)) { | |
| // treat the data we got from the cache as valid | |
| tbe.dataBlk := cache_entry.DataBlk; | |
| tbe.dataBlkValid.fillMask(); | |
| tbe.dataValid := true; | |
| } | |
| incomingTransactionStart(address, curTransitionEvent(), initial, was_retried); | |
| } | |
| action(Initiate_Snoop, desc="") { | |
| State initial := getState(tbe, cache_entry, address); | |
| peek(snpRdyPort, CHIRequestMsg) { | |
| set_tbe(allocateSnoopTBE(address, in_msg)); | |
| } | |
| copyCacheAndDir(cache_entry, getDirEntry(address), tbe, initial); | |
| // if we end up with valid data drop it if no entry allocated | |
| tbe.dataToBeInvalid := is_invalid(cache_entry); | |
| // model the initial tag array read | |
| tbe.actions.pushNB(Event:TagArrayRead); | |
| incomingTransactionStart(address, curTransitionEvent(), initial, false); | |
| } | |
| action(Initiate_Snoop_Hazard, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.is_req_tbe || tbe.is_repl_tbe); | |
| // Switch to the new snoop TBE | |
| TBE prev_tbe := tbe; | |
| peek(snpRdyPort, CHIRequestMsg) { | |
| set_tbe(allocateSnoopTBE(address, in_msg)); | |
| } | |
| assert(tbe.is_snp_tbe); | |
| if (prev_tbe.is_req_tbe) { | |
| assert(prev_tbe.is_repl_tbe == false); | |
| tbe.is_req_hazard := true; | |
| } else { | |
| assert(prev_tbe.is_repl_tbe); | |
| tbe.is_repl_hazard := true; | |
| } | |
| // Use state from prev TBE | |
| tbe.pendReqType := prev_tbe.pendReqType; | |
| copyCacheAndDirTBEs(prev_tbe, tbe); | |
| tbe.wakeup_pending_req := prev_tbe.wakeup_pending_req; | |
| tbe.wakeup_pending_snp := prev_tbe.wakeup_pending_snp; | |
| tbe.wakeup_pending_tgr := prev_tbe.wakeup_pending_tgr; | |
| } | |
| action(RestoreFromHazard, desc="") { | |
| TBE hazard_tbe := getHazardTBE(tbe); | |
| // update | |
| setDataToBeStates(tbe); | |
| copyCacheAndDirTBEs(tbe, hazard_tbe); | |
| hazard_tbe.wakeup_pending_req := tbe.wakeup_pending_req; | |
| hazard_tbe.wakeup_pending_snp := tbe.wakeup_pending_snp; | |
| hazard_tbe.wakeup_pending_tgr := tbe.wakeup_pending_tgr; | |
| deallocateSnpTBE(tbe); | |
| set_tbe(hazard_tbe); | |
| // if the pending request is a WB or Evict then it becomes a stale request | |
| // if data is no longer in the expected state | |
| if (tbe.pendReqType == CHIRequestType:WriteBackFull) { | |
| tbe.is_stale := (tbe.dataValid && tbe.dataDirty) == false; | |
| } else if (tbe.pendReqType == CHIRequestType:WriteCleanFull) { | |
| tbe.is_stale := (tbe.dataValid && tbe.dataDirty) == false; | |
| } else if (hazard_tbe.pendReqType == CHIRequestType:WriteEvictFull) { | |
| tbe.is_stale := (tbe.dataValid && tbe.dataUnique) == false; | |
| } else if (hazard_tbe.pendReqType == CHIRequestType:Evict) { | |
| tbe.is_stale := tbe.dataValid == false; | |
| } else if (hazard_tbe.pendReqType == CHIRequestType:CleanUnique) { | |
| tbe.is_stale := tbe.dataValid == false; | |
| } | |
| // a pending action from the original request may have been stalled during | |
| // the hazard and needs to wakeup up now | |
| wakeupPendingTgrs(tbe); | |
| } | |
| action(Initiate_Replacement, desc="") { | |
| assert(is_invalid(tbe)); | |
| State initial := getState(tbe, cache_entry, address); | |
| if (unify_repl_TBEs) { | |
| peek(replTriggerInPort, ReplacementMsg) { | |
| set_tbe(allocateReplacementTBEOnSlot(address, in_msg.slot)); | |
| DPRINTF(RubySlicc, "Allocated replacement TBE on slot %d\n", tbe.storSlot); | |
| } | |
| } else { | |
| set_tbe(allocateReplacementTBE(address)); | |
| DPRINTF(RubySlicc, "Allocated replacement TBE on new slot %d\n", tbe.storSlot); | |
| } | |
| copyCacheAndDir(cache_entry, getDirEntry(address), tbe, initial); | |
| // model the initial tag array read | |
| tbe.actions.pushNB(Event:TagArrayRead); | |
| incomingTransactionStart(address, curTransitionEvent(), initial, false); | |
| } | |
| action(StallRequest, desc="") { | |
| // was stalled because of an existing request | |
| assert(is_valid(tbe)); | |
| assert(tbe.addr == address); | |
| // tracks pending | |
| tbe.wakeup_pending_req := true; | |
| stall_and_wait(reqRdyPort, address); | |
| } | |
| action(StallSnoop, desc="") { | |
| // was stalled because of an existing request | |
| assert(is_valid(tbe)); | |
| assert(tbe.addr == address); | |
| // tracks pending | |
| tbe.wakeup_pending_snp := true; | |
| stall_and_wait(snpRdyPort, address); | |
| } | |
| action(StallLocalEviction, desc="") { | |
| // was stalled because of an existing request | |
| assert(is_valid(tbe)); | |
| assert(tbe.addr == address); | |
| // Just pop the queue and When this transaction finishes wake-up the original | |
| // msgs that caused this eviction | |
| tbe.wakeup_pending_tgr := true; | |
| replTriggerInPort.dequeue(clockEdge()); | |
| } | |
| action(StallSnoop_NoTBE, desc="") { | |
| stall_and_wait(snpRdyPort, address); | |
| } | |
| action(StallActionOnHazard, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.is_req_hazard || tbe.is_repl_hazard); | |
| tbe.wakeup_pending_tgr := true; | |
| stall_and_wait(triggerInPort, address); | |
| } | |
| action(Initiate_ReadShared_Miss, desc="") { | |
| tbe.actions.push(Event:ReadMissPipe); | |
| if (is_HN && tbe.use_DMT) { | |
| tbe.requestorToBeExclusiveOwner := true; | |
| tbe.dataMaybeDirtyUpstream := true; // SNF always replies with CompData_UC | |
| if (enable_DMT_early_dealloc) { | |
| tbe.actions.push(Event:SendRespSepData); | |
| } | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendReadNoSnpDMT); | |
| } else if (is_HN) { | |
| tbe.actions.push(Event:SendReadNoSnp); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } else { | |
| tbe.actions.push(Event:SendReadShared); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| action(Initiate_ReadShared_Hit, desc="") { | |
| tbe.actions.push(Event:ReadHitPipe); | |
| tbe.actions.push(Event:DataArrayRead); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_ReadShared_HitUpstream, desc="") { | |
| tbe.actions.push(Event:ReadMissPipe); | |
| if (tbe.use_DCT) { | |
| tbe.actions.push(Event:SendSnpSharedFwdToOwner); | |
| tbe.actions.pushNB(Event:WaitCompAck); | |
| tbe.updateDirOnCompAck := false; | |
| } else { | |
| tbe.actions.push(Event:SendSnpShared); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } | |
| tbe.actions.push(Event:MaintainCoherence); | |
| } | |
| action(Initiate_ReadShared_HitUpstream_NoOwner, desc="") { | |
| tbe.actions.push(Event:ReadMissPipe); | |
| if (tbe.use_DCT) { | |
| tbe.actions.push(Event:SendSnpSharedFwdToSharer); | |
| tbe.actions.pushNB(Event:WaitCompAck); | |
| tbe.updateDirOnCompAck := false; | |
| } else { | |
| tbe.actions.push(Event:SendSnpOnce); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } | |
| tbe.actions.push(Event:MaintainCoherence); | |
| } | |
| action(Initiate_ReadOnce_Miss, desc="") { | |
| // drop at the end if not doing a fill | |
| tbe.dataToBeInvalid := tbe.doCacheFill == false; | |
| tbe.actions.push(Event:ReadMissPipe); | |
| if (is_HN && tbe.use_DMT) { | |
| assert(is_invalid(cache_entry)); | |
| tbe.requestorToBeExclusiveOwner := true; | |
| tbe.dataMaybeDirtyUpstream := true; // SNF always replies with CompData_UC | |
| if (enable_DMT_early_dealloc) { | |
| tbe.actions.push(Event:SendRespSepData); | |
| } | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendReadNoSnpDMT); | |
| } else if (is_HN) { | |
| tbe.actions.push(Event:SendReadNoSnp); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } else { | |
| // if not allocating an entry send a ReadOnce | |
| if (tbe.dataToBeInvalid) { | |
| tbe.actions.push(Event:SendReadOnce); | |
| } else { | |
| tbe.actions.push(Event:SendReadShared); | |
| } | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } | |
| tbe.updateDirOnCompAck := false; | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| action(Initiate_ReadOnce_Hit, desc="") { | |
| tbe.actions.push(Event:ReadHitPipe); | |
| tbe.actions.push(Event:DataArrayRead); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| tbe.updateDirOnCompAck := false; | |
| } | |
| action(Initiate_ReadOnce_HitUpstream, desc="") { | |
| tbe.actions.push(Event:ReadMissPipe); | |
| if (tbe.use_DCT) { | |
| tbe.actions.push(Event:SendSnpOnceFwd); | |
| tbe.actions.pushNB(Event:WaitCompAck); | |
| } else { | |
| tbe.actions.push(Event:SendSnpOnce); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } | |
| tbe.updateDirOnCompAck := false; | |
| // no need to update or access tags/data on ReadOnce served from upstream | |
| if (is_invalid(cache_entry)) { | |
| // if we receive data, invalidate at the end so it can be dropped | |
| tbe.dataToBeInvalid := true; | |
| } else if (tbe.dataValid == false) { | |
| // possible on UD_RU,UC_RU where cache_entry valid but tbe.dataValid == false | |
| // this prevents going to RU if no data is received from snoop | |
| tbe.dataValid := true; | |
| } | |
| } | |
| action(Initiate_ReadUnique_Miss, desc="") { | |
| tbe.actions.push(Event:ReadMissPipe); | |
| if (is_HN && tbe.use_DMT) { | |
| tbe.requestorToBeExclusiveOwner := true; | |
| tbe.dataMaybeDirtyUpstream := true; // SNF always replies with CompData_UC | |
| if (enable_DMT_early_dealloc) { | |
| tbe.actions.push(Event:SendRespSepData); | |
| } | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendReadNoSnpDMT); | |
| } else if (is_HN) { | |
| tbe.actions.push(Event:SendReadNoSnp); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } else { | |
| tbe.actions.push(Event:SendReadUnique); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| action(Initiate_ReadUnique_AutoUpgrade, desc="") { | |
| assert(is_HN); | |
| tbe.dataUnique := true; | |
| } | |
| action(Initiate_ReadUnique_Upgrade, desc="") { | |
| // must use the transitions with auto upgrade otherwise | |
| assert(is_HN == false); | |
| assert(tbe.use_DCT == false); | |
| assert((tbe.dataValid && tbe.dataUnique) == false); | |
| assert((tbe.dir_ownerExists && tbe.dir_ownerIsExcl) == false); | |
| tbe.actions.push(Event:ReadMissPipe); | |
| if (tbe.dataMaybeDirtyUpstream) { | |
| tbe.actions.push(Event:SendSnpUnique); | |
| } else if (tbe.dir_sharers.count() > 0) { | |
| // no one will send us data unless we explicitly ask | |
| tbe.actions.push(Event:SendSnpUniqueRetToSrc); | |
| } else { | |
| assert(tbe.dataValid); | |
| } | |
| // then attempt to upgrade our data | |
| tbe.actions.push(Event:SendCleanUnique); | |
| tbe.actions.push(Event:CheckUpgrade_FromRU); | |
| // send up the upgraded data or fresh data if we failed, see CheckUpgrade_FromRU | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| action(Initiate_ReadUnique_Hit, desc="") { | |
| tbe.actions.push(Event:ReadHitPipe); | |
| tbe.actions.push(Event:DataArrayRead); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_ReadUnique_HitUpstream, desc="") { | |
| tbe.actions.push(Event:ReadMissPipe); | |
| // SnpUniqueFwd can be used only if the line is cached at a single | |
| // requester; so force it off if that's the case | |
| tbe.use_DCT := tbe.use_DCT && (tbe.dir_sharers.count() == 1) && | |
| (tbe.dir_sharers.isElement(tbe.requestor) == false); | |
| if (tbe.use_DCT) { | |
| tbe.actions.push(Event:SendSnpUniqueFwd); | |
| tbe.actions.pushNB(Event:WaitCompAck); | |
| tbe.updateDirOnCompAck := false; | |
| } else if (tbe.dataMaybeDirtyUpstream) { | |
| tbe.actions.push(Event:SendSnpUnique); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } else { | |
| // no one will send us data unless we explicitly ask | |
| tbe.actions.push(Event:SendSnpUniqueRetToSrc); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| } | |
| // just tag update since data any data would become stale | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_ReadUnique_Hit_InvUpstream, desc="") { | |
| tbe.actions.push(Event:ReadHitPipe); | |
| tbe.actions.push(Event:SendSnpCleanInvalid); | |
| tbe.actions.pushNB(Event:DataArrayRead); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.pushNB(Event:SendCompData); | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_CleanUnique, desc="") { | |
| tbe.actions.push(Event:ReadMissPipe); // TODO need another latency pipe ?? | |
| // invalidates everyone except requestor | |
| if (tbe.dir_sharers.count() > 1) { | |
| tbe.actions.push(Event:SendSnpCleanInvalidNoReq); | |
| } | |
| // auto upgrade if HN | |
| tbe.dataUnique := tbe.dataUnique || is_HN; | |
| // get unique permission | |
| if (tbe.dataUnique == false) { | |
| tbe.actions.push(Event:SendCleanUnique); | |
| tbe.actions.push(Event:CheckUpgrade_FromCU); | |
| } | |
| // next actions will depend on the data state after snoops+CleanUnique | |
| tbe.actions.push(Event:FinishCleanUnique); | |
| } | |
| action(Initiate_CleanUnique_Stale, desc="") { | |
| // requestor don't have the line anymore; send response but don't update the | |
| // directory on CompAck. The requestor knows we are not tracking it and will | |
| // send a ReadUnique later | |
| tbe.actions.push(Event:SendCompUCRespStale); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.updateDirOnCompAck := false; | |
| } | |
| action(Finish_CleanUnique, desc="") { | |
| // This is should be executed at the end of a transaction | |
| assert(tbe.actions.empty()); | |
| // everyone may have been hit by an invalidation so check again | |
| if (tbe.dir_sharers.isElement(tbe.requestor) == false) { | |
| tbe.updateDirOnCompAck := false; | |
| assert(tbe.dataValid == false); | |
| assert(tbe.is_stale); | |
| tbe.is_stale := false; | |
| tbe.actions.push(Event:SendCompUCRespStale); | |
| tbe.actions.push(Event:WaitCompAck); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } else { | |
| // must be the only one in sharers map | |
| assert(tbe.dir_sharers.count() == 1); | |
| assert(tbe.dataUnique); | |
| // needed by UpdateDirState_FromReqResp triggered by the expected CompAck | |
| tbe.dataMaybeDirtyUpstream := true; | |
| tbe.requestorToBeExclusiveOwner := true; | |
| tbe.dir_ownerExists := false; | |
| tbe.actions.push(Event:SendCompUCResp); | |
| tbe.actions.push(Event:WaitCompAck); | |
| // Ensure we writeback or update the cache if the owner has data as | |
| // clean data and we have it dirty. | |
| // MaintainCoherence queues the TagArrayWrite | |
| tbe.actions.push(Event:MaintainCoherence); | |
| } | |
| } | |
| action(Initiate_LoadHit, desc="") { | |
| // Local prefetch requests do not read data array | |
| if (tbe.is_local_pf == false) { | |
| tbe.actions.push(Event:DataArrayRead); | |
| } | |
| tbe.actions.push(Event:LoadHit); | |
| } | |
| action(Initiate_LoadMiss, desc="") { | |
| if (tbe.doCacheFill) { | |
| tbe.actions.push(Event:SendReadShared); | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } else { | |
| tbe.actions.push(Event:SendReadOnce); | |
| tbe.dataToBeInvalid := true; | |
| } | |
| } | |
| action(Initiate_StoreHit, desc="") { | |
| tbe.actions.push(Event:DataArrayRead); | |
| tbe.actions.push(Event:StoreHit); | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| action(Initiate_StoreMiss, desc="") { | |
| if (tbe.doCacheFill) { | |
| tbe.actions.push(Event:SendReadUnique); | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } else { | |
| tbe.actions.push(Event:SendWriteUnique); | |
| tbe.actions.push(Event:SendWUDataCB); | |
| tbe.dataToBeInvalid := true; | |
| } | |
| } | |
| action(Initiate_StoreUpgrade, desc="") { | |
| assert(tbe.dataValid); | |
| assert(is_valid(cache_entry)); | |
| tbe.actions.push(Event:SendCleanUnique); | |
| tbe.actions.push(Event:CheckUpgrade_FromStore); | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| action(Initiate_WriteUnique_LocalWrite, desc="") { | |
| // auto-upgrade if hn but state was not unique | |
| assert(is_HN || tbe.dataUnique); | |
| tbe.dataUnique := true; | |
| if (tbe.dir_sharers.count() > 0) { | |
| tbe.actions.push(Event:SendSnpCleanInvalid); | |
| } | |
| if (comp_wu) { | |
| tbe.actions.push(Event:SendDBIDResp_WU); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| tbe.actions.pushNB(Event:SendComp_WU); | |
| } else { | |
| tbe.actions.push(Event:SendCompDBIDResp_WU); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| } | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| action(Initiate_WriteUnique_LocalWrite_AfterUpgrade, desc="") { | |
| assert(is_HN == false); | |
| assert((tbe.dataValid && tbe.dataUnique) == false); | |
| tbe.actions.push(Event:SendReadUnique); | |
| if (comp_wu) { | |
| tbe.actions.push(Event:SendDBIDResp_WU); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| tbe.actions.pushNB(Event:SendComp_WU); | |
| } else { | |
| tbe.actions.push(Event:SendCompDBIDResp_WU); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| } | |
| tbe.actions.push(Event:CheckCacheFill); | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| action(Initiate_WriteUnique_Writeback, desc="") { | |
| assert(is_HN); | |
| assert(tbe.dir_sharers.count() > 0); | |
| tbe.actions.push(Event:SendSnpUnique); | |
| if (comp_wu) { | |
| tbe.actions.push(Event:SendDBIDResp_WU); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| tbe.actions.pushNB(Event:SendWriteNoSnp); | |
| tbe.actions.pushNB(Event:SendComp_WU); | |
| } else { | |
| tbe.actions.push(Event:SendCompDBIDResp_WU); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| tbe.actions.pushNB(Event:SendWriteNoSnp); | |
| } | |
| tbe.actions.push(Event:WriteBEPipe); | |
| tbe.actions.push(Event:SendWBData); | |
| tbe.dataToBeInvalid := true; | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_WriteUnique_PartialWrite, desc="") { | |
| assert(is_HN); | |
| if (tbe.dir_sharers.count() > 0) { | |
| tbe.actions.push(Event:SendSnpCleanInvalid); | |
| } | |
| if (comp_wu) { | |
| tbe.actions.push(Event:SendDBIDResp_WU); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| tbe.actions.pushNB(Event:SendWriteNoSnpPartial); | |
| tbe.actions.pushNB(Event:SendComp_WU); | |
| } else { | |
| tbe.actions.push(Event:SendCompDBIDResp_WU); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| tbe.actions.pushNB(Event:SendWriteNoSnpPartial); | |
| } | |
| tbe.actions.push(Event:WriteBEPipe); | |
| tbe.actions.push(Event:SendWUData); | |
| tbe.dataToBeInvalid := true; | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_WriteUnique_Forward, desc="") { | |
| tbe.actions.push(Event:WriteFEPipe); | |
| tbe.actions.push(Event:SendWriteUnique); | |
| tbe.actions.push(Event:SendCompDBIDResp_WU); | |
| tbe.actions.push(Event:WriteBEPipe); | |
| tbe.actions.push(Event:SendWUData); | |
| tbe.dataToBeInvalid := true; | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_CopyBack, desc="") { | |
| // expect to receive this data after Send_CompDBIDResp | |
| if (tbe.reqType == CHIRequestType:WriteBackFull) { | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_UD_PD); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_SD_PD); | |
| } else if (tbe.reqType == CHIRequestType:WriteEvictFull) { | |
| assert(tbe.reqType == CHIRequestType:WriteEvictFull); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_UC); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_SC); | |
| } else { | |
| assert(tbe.reqType == CHIRequestType:WriteCleanFull); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_UD_PD); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_SD_PD); | |
| } | |
| tbe.expected_req_resp.setExpectedCount(1); | |
| tbe.actions.pushNB(Event:SendCompDBIDResp); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| tbe.actions.push(Event:MaintainCoherence); | |
| // MaintainCoherence queues the Tag/Data updates | |
| } | |
| action(Initiate_CopyBack_Stale, desc="") { | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_SC); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CBWrData_I); | |
| tbe.expected_req_resp.setExpectedCount(1); | |
| tbe.actions.pushNB(Event:SendCompDBIDRespStale); | |
| tbe.actions.pushNB(Event:WriteFEPipe); | |
| tbe.actions.push(Event:FinishCopyBack_Stale); | |
| assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != tbe.requestor)); | |
| } | |
| action(Finish_CopyBack_Stale, desc="") { | |
| // if it was the last known sharer and we don't have the data do the same | |
| // the Initiate_Evict | |
| if ((is_HN == false) && (tbe.dir_sharers.count() == 0) && | |
| tbe.dir_sharers.isElement(tbe.requestor) && (tbe.dataValid == false)) { | |
| tbe.actions.push(Event:SendEvict); | |
| } | |
| } | |
| action(Initiate_Evict, desc="") { | |
| if ((is_HN == false) && (tbe.dir_sharers.count() == 1) && | |
| (tbe.dataValid == false)) { | |
| // last sharer and we also don't have a copy the line, so we also need to | |
| // send an eviction downstream | |
| if (tbe.dataUnique) { | |
| // we need to send a WriteEvictFull so need the upstream data before | |
| // we ack the evict | |
| tbe.actions.push(Event:SendSnpOnce); | |
| tbe.actions.push(Event:SendCompIResp); | |
| tbe.actions.push(Event:SendWriteBackOrWriteEvict); | |
| tbe.actions.push(Event:WriteBEPipe); | |
| tbe.actions.push(Event:SendWBData); | |
| } else { | |
| tbe.actions.push(Event:SendCompIResp); | |
| tbe.actions.push(Event:SendEvict); | |
| } | |
| } else { | |
| tbe.actions.push(Event:SendCompIResp); | |
| } | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| action(Initiate_MaintainCoherence, desc="") { | |
| // issue a copy back if necessary to maintain coherence for data we are | |
| // droping. This is should be executed at the end of a transaction | |
| assert(tbe.actions.empty()); | |
| // go through either the fill or the writeback pipeline | |
| if (tbe.dataValid && tbe.dataToBeInvalid) { | |
| // we don't need to WB if the upstream is SD, because the | |
| // owner is responsible for the WB | |
| bool has_non_ex_owner := tbe.dir_ownerExists && !tbe.dir_ownerIsExcl; | |
| if (is_HN) { | |
| if (tbe.dataDirty && !has_non_ex_owner) { | |
| tbe.actions.push(Event:SendWriteNoSnp); | |
| tbe.actions.push(Event:WriteBEPipe); | |
| tbe.actions.push(Event:SendWBData); | |
| } | |
| } else { | |
| if (tbe.dir_sharers.isEmpty() && (tbe.dataDirty || tbe.dataUnique)) { | |
| assert(!has_non_ex_owner); | |
| tbe.actions.push(Event:SendWriteBackOrWriteEvict); | |
| tbe.actions.push(Event:WriteBEPipe); | |
| tbe.actions.push(Event:SendWBData); | |
| } else if (tbe.dataDirty && !has_non_ex_owner) { | |
| assert(!tbe.dir_sharers.isEmpty()); | |
| tbe.actions.push(Event:SendWriteClean); | |
| tbe.actions.push(Event:WriteBEPipe); | |
| tbe.actions.push(Event:SendWBData); | |
| } | |
| } | |
| } else if (tbe.dataValid) { | |
| tbe.actions.push(Event:CheckCacheFill); | |
| } | |
| tbe.actions.push(Event:TagArrayWrite); | |
| } | |
| // Too many common stuff between SnpUnique/SnpUniqueFwd/SnpCleanInvalid | |
| // so do one action for all of them here | |
| action(Initiate_InvalidationSnoop, desc="") { | |
| tbe.actions.push(Event:SnpInvPipe); | |
| // Propagate a snoop upwards depending on the type | |
| if (tbe.dir_sharers.count() > 0) { | |
| if ((tbe.reqType == CHIRequestType:SnpUniqueFwd) || | |
| (tbe.reqType == CHIRequestType:SnpUnique)) { | |
| if ((tbe.snpNeedsData && (tbe.dataMaybeDirtyUpstream == false)) || | |
| (tbe.dataValid == false)) { | |
| tbe.actions.push(Event:SendSnpUniqueRetToSrc); | |
| } else { | |
| tbe.actions.push(Event:SendSnpUnique); | |
| } | |
| } else { | |
| assert(tbe.reqType == CHIRequestType:SnpCleanInvalid); | |
| tbe.actions.push(Event:SendSnpCleanInvalid); | |
| } | |
| } | |
| if (tbe.reqType == CHIRequestType:SnpUniqueFwd) { | |
| tbe.actions.push(Event:SendSnpUniqueFwdCompData); | |
| } else { | |
| tbe.actions.push(Event:SendInvSnpResp); | |
| } | |
| if(tbe.is_req_hazard || tbe.is_repl_hazard) { | |
| tbe.actions.push(Event:RestoreFromHazard); | |
| } else { | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| tbe.dataToBeInvalid := true; | |
| } | |
| action(Initiate_SnpShared, desc="") { | |
| // Handles both SnpShared,SnpSharedFwd,SnpNotSharedDirtyFwd | |
| tbe.actions.push(Event:SnpSharedPipe); | |
| if (tbe.dir_ownerExists) { | |
| assert(tbe.dataMaybeDirtyUpstream); | |
| tbe.actions.push(Event:SendSnpShared); | |
| } else if (tbe.dataValid == false) { | |
| // must get a copy of shared data upstream | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| assert(tbe.dir_sharers.count() > 0); | |
| tbe.actions.push(Event:SendSnpOnce); | |
| } else { | |
| tbe.actions.push(Event:DataArrayRead); | |
| } | |
| if (tbe.reqType == CHIRequestType:SnpSharedFwd) { | |
| tbe.actions.push(Event:SendSnpSharedFwdCompData); | |
| } else if (tbe.reqType == CHIRequestType:SnpNotSharedDirtyFwd) { | |
| tbe.actions.push(Event:SendSnpNotSharedDirtyFwdCompData); | |
| } else { | |
| assert(tbe.reqType == CHIRequestType:SnpShared); | |
| tbe.actions.push(Event:SendSnpData); | |
| } | |
| if (tbe.is_req_hazard || tbe.is_repl_hazard) { | |
| tbe.actions.push(Event:RestoreFromHazard); | |
| } else { | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| tbe.dataToBeSharedClean := true; | |
| } | |
| action(Initiate_SnpOnce, desc="") { | |
| tbe.actions.push(Event:SnpOncePipe); | |
| if (tbe.dataValid == false) { | |
| assert(tbe.dir_sharers.count() > 0); | |
| tbe.actions.push(Event:SendSnpOnce); | |
| } else { | |
| tbe.actions.push(Event:DataArrayRead); | |
| } | |
| if (tbe.reqType == CHIRequestType:SnpOnceFwd) { | |
| tbe.actions.push(Event:SendSnpOnceFwdCompData); | |
| } else { | |
| assert(tbe.reqType == CHIRequestType:SnpOnce); | |
| assert(tbe.snpNeedsData); | |
| tbe.actions.push(Event:SendSnpData); | |
| } | |
| if (tbe.is_req_hazard || tbe.is_repl_hazard) { | |
| tbe.actions.push(Event:RestoreFromHazard); | |
| } else { | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| } | |
| action(Initiate_Replacement_Evict_BackInvalidte, desc="") { | |
| assert(is_HN == false); | |
| tbe.actions.push(Event:SendSnpCleanInvalid); | |
| tbe.actions.push(Event:SendEvict); | |
| tbe.dataToBeInvalid := true; | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_Replacement_Evict, desc="") { | |
| assert(is_HN == false); | |
| assert(tbe.dir_sharers.isEmpty()); | |
| tbe.actions.push(Event:SendEvict); | |
| tbe.dataToBeInvalid := true; | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_Replacement_JustDrop, desc="") { | |
| tbe.dataToBeInvalid := true; | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_Replacement_WB_BackInvalidate, desc="") { | |
| assert(tbe.dataDirty || tbe.dataUnique || tbe.dataMaybeDirtyUpstream); | |
| tbe.actions.push(Event:SendSnpCleanInvalid); | |
| tbe.actions.push(Event:WriteFEPipe); | |
| if (is_HN) { | |
| if (tbe.dataDirty || tbe.dataMaybeDirtyUpstream) { | |
| tbe.actions.push(Event:SendWriteNoSnp); | |
| } | |
| } else { | |
| tbe.actions.push(Event:SendWriteBackOrWriteEvict); | |
| } | |
| tbe.actions.pushNB(Event:DataArrayRead); | |
| tbe.actions.push(Event:WriteBEPipe); | |
| tbe.actions.push(Event:SendWBData); | |
| tbe.dataToBeInvalid := true; | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Initiate_Replacement_WB, desc="") { | |
| tbe.actions.push(Event:WriteFEPipe); | |
| if (is_HN) { | |
| assert(tbe.dataDirty); | |
| tbe.actions.push(Event:SendWriteNoSnp); | |
| } else if (tbe.dir_sharers.isEmpty()) { | |
| assert(tbe.dataDirty || tbe.dataUnique); | |
| tbe.actions.push(Event:SendWriteBackOrWriteEvict); | |
| } else { | |
| assert(tbe.dataDirty); | |
| tbe.actions.push(Event:SendWriteClean); | |
| } | |
| tbe.actions.pushNB(Event:DataArrayRead); | |
| tbe.actions.push(Event:WriteBEPipe); | |
| tbe.actions.push(Event:SendWBData); | |
| tbe.dataToBeInvalid := true; | |
| tbe.actions.pushNB(Event:TagArrayWrite); | |
| } | |
| action(Send_ReadShared, desc="") { | |
| assert(is_HN == false); | |
| assert(tbe.dataValid == false); | |
| clearExpectedReqResp(tbe); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:DataSepResp_UC); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UC); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UD_PD); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_SC); | |
| if (allow_SD) { | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_SD_PD); | |
| } | |
| // NOTE: the first CompData received counts as RespSepData | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:RespSepData); | |
| tbe.expected_req_resp.setExpectedCount(2); | |
| tbe.dataBlkValid.clear(); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| if (allow_SD) { | |
| prepareRequest(tbe, CHIRequestType:ReadShared, out_msg); | |
| } else { | |
| prepareRequest(tbe, CHIRequestType:ReadNotSharedDirty, out_msg); | |
| } | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| out_msg.dataToFwdRequestor := false; | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| } | |
| action(Send_ReadNoSnp, desc="") { | |
| assert(is_HN); | |
| assert(tbe.use_DMT == false); | |
| clearExpectedReqResp(tbe); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UC); | |
| // NOTE: the first CompData received counts as RespSepData | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:RespSepData); | |
| tbe.expected_req_resp.setExpectedCount(2); | |
| tbe.dataBlkValid.clear(); | |
| outgoingTransactionStart(address, curTransitionEvent()); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:ReadNoSnp, out_msg); | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| out_msg.dataToFwdRequestor := false; | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| } | |
| action(Send_ReadNoSnpDMT, desc="") { | |
| assert(is_HN); | |
| assert(tbe.use_DMT); | |
| CHIRequestType req := CHIRequestType:ReadNoSnp; | |
| if (enable_DMT_early_dealloc) { | |
| req := CHIRequestType:ReadNoSnpSep; | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:ReadReceipt); | |
| tbe.expected_req_resp.addExpectedCount(1); | |
| } | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, req, out_msg); | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| out_msg.dataToFwdRequestor := true; | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| } | |
| action(Send_ReadOnce, desc="") { | |
| assert(is_HN == false); | |
| assert(tbe.dataValid == false); | |
| clearExpectedReqResp(tbe); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:DataSepResp_UC); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UC); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_I); | |
| // NOTE: the first CompData received counts as RespSepData | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:RespSepData); | |
| tbe.expected_req_resp.setExpectedCount(2); | |
| tbe.dataBlkValid.clear(); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:ReadOnce, out_msg); | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| out_msg.dataToFwdRequestor := false; | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| } | |
| action(Send_ReadUnique, desc="") { | |
| assert((tbe.dataValid && tbe.dataUnique) == false); | |
| assert(tbe.expected_req_resp.hasExpected() == false); | |
| clearExpectedReqResp(tbe); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:DataSepResp_UC); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UC); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:CompData_UD_PD); | |
| // NOTE: the first CompData received counts as RespSepData | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:RespSepData); | |
| tbe.expected_req_resp.setExpectedCount(2); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:ReadUnique, out_msg); | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| out_msg.dataToFwdRequestor := false; | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| } | |
| action(Send_CleanUnique, desc="") { | |
| assert(tbe.dataValid || (tbe.dir_sharers.count() > 0)); | |
| assert(tbe.dataUnique == false); | |
| assert(tbe.expected_req_resp.hasExpected() == false); | |
| clearExpectedReqResp(tbe); | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:Comp_UC); | |
| tbe.expected_req_resp.setExpectedCount(1); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:CleanUnique, out_msg); | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| } | |
| action(Send_Evict, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(is_HN == false); | |
| assert(tbe.expected_req_resp.hasExpected() == false); | |
| clearExpectedReqResp(tbe); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:Evict, out_msg); | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:Comp_I); | |
| tbe.expected_req_resp.setExpectedCount(1); | |
| } | |
| action(Send_InvSnpResp, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| if (tbe.dataDirty || tbe.snpNeedsData || | |
| (tbe.dataUnique && (tbe.reqType == CHIRequestType:SnpUnique))) { | |
| tbe.actions.pushFront(Event:SendSnpData); | |
| } else { | |
| tbe.actions.pushFront(Event:SendSnpIResp); | |
| } | |
| } | |
| action(Send_WriteBackOrWriteEvict, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.dataBlkValid.isFull()); | |
| assert(tbe.dataValid); | |
| assert(is_HN == false); | |
| assert(tbe.dataUnique || tbe.dataDirty); | |
| assert(tbe.dir_sharers.isEmpty()); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| if (tbe.dataDirty) { | |
| prepareRequest(tbe, CHIRequestType:WriteBackFull, out_msg); | |
| } else { | |
| prepareRequest(tbe, CHIRequestType:WriteEvictFull, out_msg); | |
| } | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| clearExpectedReqResp(tbe); | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp); | |
| tbe.expected_req_resp.setExpectedCount(1); | |
| } | |
| action(Send_WriteCleanFull, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.dataBlkValid.isFull()); | |
| assert(tbe.dataValid); | |
| assert(is_HN == false); | |
| assert(tbe.dataDirty); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:WriteCleanFull, out_msg); | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| clearExpectedReqResp(tbe); | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp); | |
| tbe.expected_req_resp.setExpectedCount(1); | |
| } | |
| action(Send_WriteNoSnp, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:WriteNoSnp, out_msg); | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| // allow to expect this on top of data coming from upstream; | |
| // so addExpectedCount | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp); | |
| tbe.expected_req_resp.addExpectedCount(1); | |
| } | |
| action(Send_WriteNoSnp_Partial, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:WriteNoSnpPtl, out_msg); | |
| out_msg.accAddr := tbe.accAddr; | |
| out_msg.accSize := tbe.accSize; | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| // allow to expect this on top of data coming from upstream; | |
| // so addExpectedCount | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp); | |
| tbe.expected_req_resp.addExpectedCount(1); | |
| } | |
| action(Send_WriteUnique, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| if (tbe.accSize == blockSize) { | |
| prepareRequest(tbe, CHIRequestType:WriteUniqueFull, out_msg); | |
| } else { | |
| prepareRequest(tbe, CHIRequestType:WriteUniquePtl, out_msg); | |
| out_msg.accAddr := tbe.accAddr; | |
| out_msg.accSize := tbe.accSize; | |
| } | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| // allow to expect this on top of data coming from upstream; | |
| // so addExpectedCount | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp); | |
| // if receive only DBIDResp then will expect Comp later | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:DBIDResp); | |
| tbe.expected_req_resp.addExpectedCount(1); | |
| } | |
| action(Send_SnpCleanInvalid, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.expected_snp_resp.hasExpected() == false); | |
| // at least one sharer or owner othrwise should not execute this | |
| assert(tbe.dir_sharers.count() > 0); | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpCleanInvalid, out_msg); | |
| out_msg.Destination.addNetDest(tbe.dir_sharers); | |
| out_msg.retToSrc := false; | |
| } | |
| setExpectedForInvSnoop(tbe, false); | |
| } | |
| action(Send_SnpCleanInvalid_NoReq, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.expected_snp_resp.hasExpected() == false); | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpCleanInvalid, out_msg); | |
| out_msg.Destination.addNetDest(tbe.dir_sharers); | |
| out_msg.Destination.remove(tbe.requestor); | |
| // at least one sharer other than requestor | |
| assert(out_msg.Destination.count() > 0); | |
| out_msg.retToSrc := false; | |
| setExpectedForInvSnoop(tbe, false); | |
| tbe.expected_snp_resp.setExpectedCount(out_msg.Destination.count()); | |
| } | |
| } | |
| action(Send_SnpUnique, desc="") { | |
| assert(is_valid(tbe)); | |
| // at least one sharer or owner othrwise should not execute this | |
| assert(tbe.dir_sharers.count() > 0); | |
| setExpectedForInvSnoop(tbe, true); | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpUnique, out_msg); | |
| out_msg.Destination.addNetDest(tbe.dir_sharers); | |
| out_msg.retToSrc := false; | |
| } | |
| } | |
| action(Send_SnpUnique_RetToSrc, desc="") { | |
| assert(is_valid(tbe)); | |
| // at least one sharer or owner othrwise should not execute this | |
| assert(tbe.dir_sharers.count() > 0); | |
| setExpectedForInvSnoop(tbe, true); | |
| MachineID dest; | |
| if (tbe.dir_ownerExists) { | |
| dest := tbe.dir_owner; | |
| } else { | |
| // TODO should be random or the closest one | |
| dest := tbe.dir_sharers.smallestElement(); | |
| } | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpUnique, out_msg); | |
| out_msg.Destination.add(dest); | |
| out_msg.retToSrc := true; | |
| } | |
| // if other sharers send with retToSrc=false to others | |
| if (tbe.dir_sharers.count() > 1) { | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpUnique, out_msg); | |
| out_msg.Destination.addNetDest(tbe.dir_sharers); | |
| out_msg.Destination.remove(dest); | |
| out_msg.retToSrc := false; | |
| } | |
| } | |
| } | |
| action(Send_SnpUniqueFwd, desc="") { | |
| assert(is_valid(tbe)); | |
| // single sharer or owner otherwise should not execute this | |
| assert(tbe.dir_sharers.count() == 1); | |
| assert(tbe.expected_snp_resp.expected() == 0); | |
| clearExpectedSnpResp(tbe); | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_I_Fwded_UC); | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_I_Fwded_UD_PD); | |
| tbe.expected_snp_resp.addExpectedCount(1); | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpUniqueFwd, out_msg); | |
| out_msg.Destination.addNetDest(tbe.dir_sharers); | |
| out_msg.retToSrc := false; | |
| } | |
| } | |
| action(Send_SnpShared, desc="") { | |
| assert(is_valid(tbe)); | |
| // only sent to a dirty or exclusive snoopee | |
| assert(tbe.dataMaybeDirtyUpstream); | |
| assert(tbe.dir_ownerExists); | |
| assert(tbe.dir_sharers.count() > 0); | |
| assert(tbe.expected_snp_resp.expected() == 0); | |
| clearExpectedSnpResp(tbe); | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC); | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_PD); | |
| tbe.expected_snp_resp.setExpectedCount(1); | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpShared, out_msg); | |
| out_msg.Destination.add(tbe.dir_owner); | |
| out_msg.retToSrc := false; | |
| } | |
| } | |
| action(Send_SnpSharedFwd_ToOwner, desc="") { | |
| assert(is_valid(tbe)); | |
| // the dirty snoopee must go to SC and send data | |
| assert(tbe.dataMaybeDirtyUpstream); | |
| assert(tbe.dir_ownerExists); | |
| assert(tbe.dir_sharers.count() > 0); | |
| assert(tbe.expected_snp_resp.expected() == 0); | |
| clearExpectedSnpResp(tbe); | |
| bool allowFwdSD := tbe.reqType != CHIRequestType:ReadNotSharedDirty; | |
| // get us a copy if we have allocated a cache entry for this block | |
| bool retToSrc := tbe.doCacheFill && (tbe.dataToBeInvalid == false); | |
| if (allowFwdSD) { | |
| if (retToSrc) { | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_Fwded_SC); | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_Fwded_SD_PD); | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I_Fwded_SC); | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I_Fwded_SD_PD); | |
| } else { | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_SC); | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_SD_PD); | |
| } | |
| } else { | |
| if (retToSrc) { | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_Fwded_SC); | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I_Fwded_SC); | |
| } else { | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_SC); | |
| } | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_PD_Fwded_SC); | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_I_PD_Fwded_SC); | |
| } | |
| tbe.expected_snp_resp.addExpectedCount(1); | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| if (allowFwdSD) { | |
| prepareRequest(tbe, CHIRequestType:SnpSharedFwd, out_msg); | |
| } else { | |
| prepareRequest(tbe, CHIRequestType:SnpNotSharedDirtyFwd, out_msg); | |
| } | |
| out_msg.Destination.add(tbe.dir_owner); | |
| out_msg.retToSrc := retToSrc; | |
| } | |
| } | |
| action(Send_SnpSharedFwd_ToSharer, desc="") { | |
| assert(is_valid(tbe)); | |
| // send to onde of the sharers with shared clean data | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| assert(tbe.dir_ownerExists == false); | |
| assert(tbe.dir_sharers.count() > 0); | |
| assert(tbe.expected_snp_resp.expected() == 0); | |
| clearExpectedSnpResp(tbe); | |
| // if we have a block allocated for this line, asks snoopee to forward | |
| // data to us as well | |
| bool retToSrc := tbe.doCacheFill; | |
| if (retToSrc) { | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC_Fwded_SC); | |
| } else { | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_SC); | |
| } | |
| tbe.expected_snp_resp.addExpectedCount(1); | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpSharedFwd, out_msg); | |
| // TODO should be random or the closest one to the fwd dest | |
| out_msg.Destination.add(tbe.dir_sharers.smallestElement()); | |
| out_msg.retToSrc := retToSrc; | |
| } | |
| } | |
| action(Send_SnpOnce, desc="") { | |
| assert(is_valid(tbe)); | |
| // send to one of the sharers or owner to get a copy of the line | |
| assert(tbe.dir_sharers.count() > 0); | |
| assert(tbe.expected_snp_resp.expected() == 0); | |
| clearExpectedSnpResp(tbe); | |
| if (tbe.dir_ownerExists) { | |
| if (tbe.dir_ownerIsExcl) { | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_UC); | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_UD); | |
| } else { | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SD); | |
| } | |
| } else { | |
| tbe.expected_snp_resp.addExpectedDataType(CHIDataType:SnpRespData_SC); | |
| } | |
| tbe.expected_snp_resp.addExpectedCount(1); | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpOnce, out_msg); | |
| if (tbe.dir_ownerExists) { | |
| out_msg.Destination.add(tbe.dir_owner); | |
| } else { | |
| // TODO should be random or the closest one | |
| out_msg.Destination.add(tbe.dir_sharers.smallestElement()); | |
| } | |
| out_msg.retToSrc := true; | |
| } | |
| } | |
| action(Send_SnpOnceFwd, desc="") { | |
| assert(is_valid(tbe)); | |
| // send to one of the sharers or owner to get a copy of the line | |
| assert(tbe.dir_sharers.count() > 0); | |
| assert(tbe.expected_snp_resp.expected() == 0); | |
| clearExpectedSnpResp(tbe); | |
| if (tbe.dir_ownerExists) { | |
| if (tbe.dir_ownerIsExcl) { | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_UC_Fwded_I); | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_UD_Fwded_I); | |
| } else { | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SD_Fwded_I); | |
| } | |
| } else { | |
| tbe.expected_snp_resp.addExpectedRespType(CHIResponseType:SnpResp_SC_Fwded_I); | |
| } | |
| tbe.expected_snp_resp.addExpectedCount(1); | |
| enqueue(snpOutPort, CHIRequestMsg, snoop_latency) { | |
| prepareRequest(tbe, CHIRequestType:SnpOnceFwd, out_msg); | |
| if (tbe.dir_ownerExists) { | |
| out_msg.Destination.add(tbe.dir_owner); | |
| } else { | |
| // TODO should be random or the closest one | |
| out_msg.Destination.add(tbe.dir_sharers.smallestElement()); | |
| } | |
| out_msg.retToSrc := false; | |
| } | |
| } | |
| action(ExpectNCBWrData, desc="") { | |
| // Expected data | |
| int num_msgs := tbe.accSize / data_channel_size; | |
| if ((tbe.accSize % data_channel_size) != 0) { | |
| num_msgs := num_msgs + 1; | |
| } | |
| tbe.expected_req_resp.clear(num_msgs); | |
| tbe.expected_req_resp.addExpectedDataType(CHIDataType:NCBWrData); | |
| tbe.expected_req_resp.setExpectedCount(1); | |
| // Clear the mask bits we expect to receive | |
| tbe.dataBlkValid.setMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize, false); | |
| } | |
| action(ExpectCompAck, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompAck); | |
| tbe.expected_req_resp.addExpectedCount(1); | |
| } | |
| action(ExpectComp, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:Comp); | |
| tbe.expected_req_resp.addExpectedCount(1); | |
| } | |
| action(Receive_ReqDataResp, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.expected_req_resp.hasExpected()); | |
| peek(datInPort, CHIDataMsg) { | |
| // Decrement pending | |
| if (tbe.expected_req_resp.receiveData(in_msg.type) == false) { | |
| error("Received unexpected message"); | |
| } | |
| // Copy data to tbe only if we didn't have valid data or the received | |
| // data is dirty | |
| if ((tbe.dataBlkValid.isFull() == false) || | |
| (in_msg.type == CHIDataType:CompData_UD_PD) || | |
| (in_msg.type == CHIDataType:CompData_SD_PD) || | |
| (in_msg.type == CHIDataType:CBWrData_UD_PD) || | |
| (in_msg.type == CHIDataType:CBWrData_SD_PD) || | |
| (in_msg.type == CHIDataType:NCBWrData)) { | |
| // clear mask if started to receive new data | |
| if(tbe.dataBlkValid.isFull()){ | |
| tbe.dataBlkValid.clear(); | |
| } | |
| tbe.dataBlk.copyPartial(in_msg.dataBlk, in_msg.bitMask); | |
| assert(tbe.dataBlkValid.isOverlap(in_msg.bitMask) == false); | |
| tbe.dataBlkValid.orMask(in_msg.bitMask); | |
| } | |
| } | |
| } | |
| action(Receive_RespSepDataFromCompData, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.expected_req_resp.hasExpected()); | |
| // check if a previous CompData msg already counted as a RespSepData | |
| if (tbe.expected_req_resp.receivedRespType(CHIResponseType:RespSepData) == false) { | |
| if (tbe.expected_req_resp.receiveResp(CHIResponseType:RespSepData) == false) { | |
| error("Received unexpected message"); | |
| } | |
| if (is_HN == false) { | |
| // must now ack the responder | |
| tbe.actions.pushFrontNB(Event:SendCompAck); | |
| } | |
| } | |
| } | |
| action(Receive_RespSepData, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.expected_req_resp.hasExpected()); | |
| if (tbe.expected_req_resp.receiveResp(CHIResponseType:RespSepData) == false) { | |
| error("Received unexpected message"); | |
| } | |
| if (is_HN == false) { | |
| // must now ack the responder | |
| tbe.actions.pushFrontNB(Event:SendCompAck); | |
| } | |
| } | |
| action(Receive_ReadReceipt, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.expected_req_resp.hasExpected()); | |
| if (tbe.expected_req_resp.receiveResp(CHIResponseType:ReadReceipt) == false) { | |
| error("Received unexpected message"); | |
| } | |
| } | |
| action(Receive_SnpDataResp, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.expected_snp_resp.hasExpected()); | |
| peek(datInPort, CHIDataMsg) { | |
| // Decrement pending | |
| if (tbe.expected_snp_resp.receiveData(in_msg.type) == false) { | |
| error("Received unexpected message"); | |
| } | |
| // Copy data to tbe only if we didn't have valid data or the received | |
| // data is dirty | |
| if ((tbe.dataBlkValid.isFull() == false) || | |
| (in_msg.type == CHIDataType:SnpRespData_I_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_PD_Fwded_SC) || | |
| (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SD_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_I_PD_Fwded_SC)) { | |
| // clear mask if started to receive new data | |
| if(tbe.dataBlkValid.isFull()){ | |
| tbe.dataBlkValid.clear(); | |
| } | |
| tbe.dataBlk.copyPartial(in_msg.dataBlk, in_msg.bitMask); | |
| assert(tbe.dataBlkValid.isOverlap(in_msg.bitMask) == false); | |
| tbe.dataBlkValid.orMask(in_msg.bitMask); | |
| } | |
| } | |
| } | |
| action(UpdateDirState_FromReqDataResp, desc="") { | |
| assert(is_valid(tbe)); | |
| // only perform the update once we received all chunks | |
| if (tbe.expected_req_resp.hasReceivedData()) { | |
| assert(tbe.dataBlkValid.isFull()); | |
| peek(datInPort, CHIDataMsg) { | |
| if (in_msg.type == CHIDataType:CBWrData_UC) { | |
| assert(tbe.dir_ownerExists && tbe.dir_ownerIsExcl && (tbe.dir_owner == in_msg.responder)); | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| tbe.dir_ownerExists := false; | |
| tbe.dir_ownerIsExcl := false; | |
| tbe.dir_sharers.remove(in_msg.responder); | |
| } else if (in_msg.type == CHIDataType:CBWrData_UD_PD) { | |
| assert(tbe.dir_ownerExists && tbe.dir_ownerIsExcl && (tbe.dir_owner == in_msg.responder)); | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| if (tbe.reqType != CHIRequestType:WriteCleanFull) { | |
| tbe.dir_ownerExists := false; | |
| tbe.dir_ownerIsExcl := false; | |
| tbe.dir_sharers.remove(in_msg.responder); | |
| } | |
| } else if (in_msg.type == CHIDataType:CBWrData_SC) { | |
| assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != in_msg.responder)); | |
| // Do not remove the responder in case of stale WriteCleanFull | |
| if (tbe.reqType != CHIRequestType:WriteCleanFull) { | |
| tbe.dir_sharers.remove(in_msg.responder); | |
| } | |
| } else if (in_msg.type == CHIDataType:CBWrData_SD_PD) { | |
| assert(tbe.dir_ownerExists && (tbe.dir_ownerIsExcl == false) && (tbe.dir_owner == in_msg.responder)); | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| tbe.dir_ownerExists := false; | |
| tbe.dir_ownerIsExcl := false; | |
| if (tbe.reqType != CHIRequestType:WriteCleanFull) { | |
| tbe.dir_sharers.remove(in_msg.responder); | |
| } | |
| } else if (in_msg.type == CHIDataType:CBWrData_I) { | |
| // nothing to do here; just check | |
| assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != in_msg.responder)); | |
| assert(tbe.dir_sharers.isElement(in_msg.responder) == false); | |
| } else { | |
| error("Unsuported data type"); | |
| } | |
| } | |
| } | |
| printTBEState(tbe); | |
| } | |
| action(UpdateDirState_FromSnpDataResp, desc="") { | |
| assert(is_valid(tbe)); | |
| // only perform the update once we received all chunks | |
| if (tbe.expected_snp_resp.hasReceivedData()) { | |
| assert(tbe.dataBlkValid.isFull()); | |
| peek(datInPort, CHIDataMsg) { | |
| if (in_msg.type == CHIDataType:SnpRespData_I) { | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| tbe.dir_ownerExists := false; | |
| tbe.dir_ownerIsExcl := false; | |
| tbe.dir_sharers.remove(in_msg.responder); | |
| } else if (in_msg.type == CHIDataType:SnpRespData_I_PD) { | |
| assert(tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder)); | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| tbe.dir_ownerExists := false; | |
| tbe.dir_ownerIsExcl := false; | |
| tbe.dir_sharers.remove(in_msg.responder); | |
| } else if ((in_msg.type == CHIDataType:SnpRespData_SC_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SC) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_PD_Fwded_SC)) { | |
| // the owner must have been the responder, if there was one | |
| assert((tbe.dir_ownerExists == false) || | |
| (tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder))); | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| tbe.dir_ownerExists := false; | |
| tbe.dir_ownerIsExcl := false; | |
| if ((in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SC) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_PD_Fwded_SC) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD)) { | |
| tbe.dir_sharers.add(tbe.requestor); | |
| } | |
| if (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) { | |
| tbe.dir_ownerExists := true; | |
| tbe.dir_owner := tbe.requestor; | |
| } | |
| } else if ((in_msg.type == CHIDataType:SnpRespData_I_Fwded_SD_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_I_PD_Fwded_SC) || | |
| (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SC)) { | |
| // the owner must have been the responder, if there was one | |
| assert((tbe.dir_ownerExists == false) || | |
| (tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder))); | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| tbe.dir_ownerExists := false; | |
| tbe.dir_ownerIsExcl := false; | |
| tbe.dir_sharers.remove(in_msg.responder); | |
| tbe.dir_sharers.add(tbe.requestor); | |
| if (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SD_PD) { | |
| tbe.dir_ownerExists := true; | |
| tbe.dir_owner := tbe.requestor; | |
| } | |
| } else if ((in_msg.type == CHIDataType:SnpRespData_SD) || | |
| (in_msg.type == CHIDataType:SnpRespData_UC) || | |
| (in_msg.type == CHIDataType:SnpRespData_UD)) { | |
| // expected only in response to a SnpOnce; just do some checks | |
| // also may get SnpRespData_SC, but handled properly above | |
| assert(tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder)); | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| } else { | |
| error("Unsuported data type"); | |
| } | |
| } | |
| } | |
| printTBEState(tbe); | |
| } | |
| action(UpdateDataState_FromReqDataResp, desc="") { | |
| assert(is_valid(tbe)); | |
| // only perform the update once we received all chunks | |
| if (tbe.expected_req_resp.hasReceivedData()) { | |
| assert(tbe.dataBlkValid.isFull()); | |
| peek(datInPort, CHIDataMsg) { | |
| if ((in_msg.type == CHIDataType:CompData_UC) || | |
| (in_msg.type == CHIDataType:DataSepResp_UC)) { | |
| assert(tbe.dataUnique == false); | |
| assert((tbe.dataValid && tbe.dataDirty) == false); | |
| tbe.dataDirty := false; | |
| tbe.dataUnique := true; | |
| tbe.dataValid := true; | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| } else if (in_msg.type == CHIDataType:CompData_UD_PD) { | |
| assert(tbe.dataUnique == false); | |
| assert((tbe.dataValid && tbe.dataDirty) == false); | |
| tbe.dataDirty := true; | |
| tbe.dataUnique := true; | |
| tbe.dataValid := true; | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| } else if (in_msg.type == CHIDataType:CompData_SC) { | |
| assert(tbe.dataUnique == false); | |
| assert((tbe.dataValid && tbe.dataDirty) == false); | |
| tbe.dataDirty := false; | |
| tbe.dataUnique := false; | |
| tbe.dataValid := true; | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| } else if (in_msg.type == CHIDataType:CompData_SD_PD) { | |
| assert(tbe.dataUnique == false); | |
| assert((tbe.dataValid && tbe.dataDirty) == false); | |
| tbe.dataDirty := true; | |
| tbe.dataUnique := false; | |
| tbe.dataValid := true; | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| } else if (in_msg.type == CHIDataType:CompData_I) { | |
| tbe.dataValid := true; | |
| tbe.dataToBeInvalid := true; | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| } else if (in_msg.type == CHIDataType:CBWrData_UC) { | |
| assert(tbe.dataUnique); | |
| tbe.dataMaybeDirtyUpstream := false; | |
| tbe.dataValid := true; | |
| } else if (in_msg.type == CHIDataType:CBWrData_SC) { | |
| // stale WB, nothing to do ?? | |
| } else if (in_msg.type == CHIDataType:CBWrData_UD_PD) { | |
| assert(tbe.dataUnique); | |
| tbe.dataDirty := true; | |
| tbe.dataValid := true; | |
| if (tbe.reqType == CHIRequestType:WriteCleanFull) { | |
| // upstream data can still be UC if this is a WriteCleanFull | |
| assert(tbe.dir_ownerExists && tbe.dir_ownerIsExcl); | |
| tbe.dataMaybeDirtyUpstream := true; | |
| } else { | |
| tbe.dataMaybeDirtyUpstream := false; | |
| } | |
| } else if (in_msg.type == CHIDataType:CBWrData_SD_PD) { | |
| tbe.dataDirty := true; | |
| tbe.dataValid := true; | |
| tbe.dataMaybeDirtyUpstream := false; | |
| } else if (in_msg.type == CHIDataType:CBWrData_I) { | |
| // stale WB, nothing to do ?? | |
| } else { | |
| error("Unsuported data type"); | |
| } | |
| } | |
| } | |
| printTBEState(tbe); | |
| } | |
| action(UpdateDataState_FromWUDataResp, desc="") { | |
| assert(is_valid(tbe)); | |
| if (tbe.expected_req_resp.hasReceivedData()) { | |
| assert(tbe.dataBlkValid.test(addressOffset(tbe.accAddr, tbe.addr))); | |
| assert(tbe.dataBlkValid.test(addressOffset(tbe.accAddr, tbe.addr) | |
| + tbe.accSize - 1)); | |
| peek(datInPort, CHIDataMsg) { | |
| assert(in_msg.type == CHIDataType:NCBWrData); | |
| tbe.dataDirty := true; | |
| if (tbe.reqType == CHIRequestType:WriteUniquePtl) { | |
| // we are just updating any valid data we already had | |
| tbe.dataValid := tbe.dataValid || (tbe.accSize == blockSize); | |
| } else { | |
| tbe.dataValid := tbe.accSize == blockSize; | |
| } | |
| } | |
| } | |
| printTBEState(tbe); | |
| } | |
| action(UpdateDataState_FromCUResp, desc="") { | |
| assert(is_valid(tbe)); | |
| peek(rspInPort, CHIResponseMsg) { | |
| assert(in_msg.type == CHIResponseType:Comp_UC); | |
| assert(tbe.dataUnique == false); | |
| tbe.dataUnique := tbe.dataValid || (tbe.dir_sharers.count() > 0); | |
| // self and upstream may have been invalidated while waiting for this | |
| // expect to follow up with a ReadUnique | |
| } | |
| printTBEState(tbe); | |
| } | |
| action(UpdateDataState_FromSnpDataResp, desc="") { | |
| assert(is_valid(tbe)); | |
| // only perform the update once we received all chunks | |
| if (tbe.expected_snp_resp.hasReceivedData()) { | |
| assert(tbe.dataBlkValid.isFull()); | |
| peek(datInPort, CHIDataMsg) { | |
| if ((in_msg.type == CHIDataType:SnpRespData_I_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_PD_Fwded_SC) || | |
| (in_msg.type == CHIDataType:SnpRespData_I_PD_Fwded_SC)) { | |
| tbe.dataDirty := true; | |
| tbe.dataValid := true; | |
| tbe.dataMaybeDirtyUpstream := false; | |
| } else if ((in_msg.type == CHIDataType:SnpRespData_SD) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SD_PD) || | |
| (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SD_PD)) { | |
| tbe.dataDirty := true; | |
| tbe.dataValid := true; | |
| tbe.dataMaybeDirtyUpstream := true; | |
| } else if ((in_msg.type == CHIDataType:SnpRespData_I) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC) || | |
| (in_msg.type == CHIDataType:SnpRespData_SC_Fwded_SC) || | |
| (in_msg.type == CHIDataType:SnpRespData_I_Fwded_SC)) { | |
| tbe.dataValid := true; | |
| tbe.dataMaybeDirtyUpstream := false; | |
| } else if ((in_msg.type == CHIDataType:SnpRespData_UC) || | |
| (in_msg.type == CHIDataType:SnpRespData_UD)) { | |
| tbe.dataValid := true; | |
| tbe.dataUnique := true; | |
| tbe.dataMaybeDirtyUpstream := true; | |
| if (in_msg.type == CHIDataType:SnpRespData_UD){ | |
| tbe.dataDirty := true; | |
| } | |
| } else { | |
| error("Unsuported data type"); | |
| } | |
| } | |
| } | |
| printTBEState(tbe); | |
| } | |
| action(UpdateDirState_FromReqResp, desc="") { | |
| peek(rspInPort, CHIResponseMsg) { | |
| if ((in_msg.type == CHIResponseType:CompAck) && tbe.updateDirOnCompAck) { | |
| assert(tbe.requestor == in_msg.responder); | |
| tbe.dir_sharers.add(in_msg.responder); | |
| if (tbe.requestorToBeOwner) { | |
| assert(tbe.dataMaybeDirtyUpstream); | |
| assert(tbe.dir_ownerExists == false); | |
| assert(tbe.requestorToBeExclusiveOwner == false); | |
| tbe.dir_owner := in_msg.responder; | |
| tbe.dir_ownerExists := true; | |
| tbe.dir_ownerIsExcl := false; | |
| } else if (tbe.requestorToBeExclusiveOwner) { | |
| assert(tbe.dataMaybeDirtyUpstream); | |
| assert(tbe.dir_ownerExists == false); | |
| assert(tbe.dir_sharers.count() == 1); | |
| tbe.dir_owner := in_msg.responder; | |
| tbe.dir_ownerExists := true; | |
| tbe.dir_ownerIsExcl := true; | |
| } | |
| } | |
| } | |
| printTBEState(tbe); | |
| } | |
| action(UpdateDirState_FromSnpResp, desc="") { | |
| peek(rspInPort, CHIResponseMsg) { | |
| if (in_msg.type == CHIResponseType:SnpResp_I) { | |
| // must have been a known sharer otherwise we would receive data | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| tbe.dir_sharers.remove(in_msg.responder); | |
| if (tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder)){ | |
| tbe.dir_ownerExists := false; | |
| } | |
| } else if (in_msg.type == CHIResponseType:SnpResp_SC) { | |
| // expected from a sharer that already has it in shared state | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != in_msg.responder)); | |
| } else if ((in_msg.type == CHIResponseType:SnpResp_SC_Fwded_SC) || | |
| (in_msg.type == CHIResponseType:SnpResp_SC_Fwded_SD_PD)) { | |
| // the SnpSharedFwd must have been sent to the owner if there was one | |
| assert((tbe.dir_ownerExists == false) || | |
| (tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder))); | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| tbe.dir_ownerExists := false; | |
| tbe.dir_ownerIsExcl := false; | |
| tbe.dir_sharers.add(tbe.requestor); | |
| if (in_msg.type == CHIResponseType:SnpResp_SC_Fwded_SD_PD) { | |
| // Requestor is new owner | |
| tbe.dir_ownerExists := true; | |
| tbe.dir_owner := tbe.requestor; | |
| } | |
| } else if ((in_msg.type == CHIResponseType:SnpResp_I_Fwded_UC) || | |
| (in_msg.type == CHIResponseType:SnpResp_I_Fwded_UD_PD)) { | |
| // must have been a single sharer that received SnpUniqueFwd | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| assert(tbe.dir_sharers.count() == 1); | |
| tbe.dir_sharers.remove(in_msg.responder); | |
| // requestor is the new owner | |
| tbe.dir_sharers.add(tbe.requestor); | |
| tbe.dir_ownerExists := true; | |
| tbe.dir_ownerIsExcl := true; | |
| tbe.dir_owner := tbe.requestor; | |
| } else if ((in_msg.type == CHIResponseType:SnpResp_UC_Fwded_I) || | |
| (in_msg.type == CHIResponseType:SnpResp_UD_Fwded_I) || | |
| (in_msg.type == CHIResponseType:SnpResp_SD_Fwded_I)) { | |
| // SnpSharedFwd; just confirm | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| assert(tbe.dir_ownerExists && (tbe.dir_owner == in_msg.responder)); | |
| } else if (in_msg.type == CHIResponseType:SnpResp_SC_Fwded_I) { | |
| // SnpSharedFwd; just confirm | |
| assert(tbe.dir_sharers.isElement(in_msg.responder)); | |
| assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != in_msg.responder)); | |
| } | |
| tbe.dataMaybeDirtyUpstream := tbe.dir_ownerExists; | |
| } | |
| printTBEState(tbe); | |
| } | |
| action(Receive_ReqResp, desc="") { | |
| assert(tbe.expected_req_resp.hasExpected()); | |
| peek(rspInPort, CHIResponseMsg) { | |
| // Decrement pending | |
| if (tbe.expected_req_resp.receiveResp(in_msg.type) == false) { | |
| error("Received unexpected message"); | |
| } | |
| assert(in_msg.stale == tbe.is_stale); | |
| } | |
| } | |
| action(Receive_ReqResp_WUNeedComp, desc="") { | |
| tbe.defer_expected_comp := true; | |
| } | |
| action(Receive_ReqResp_WUComp, desc="") { | |
| if (tbe.defer_expected_comp) { | |
| tbe.defer_expected_comp := false; | |
| } else if (tbe.expected_req_resp.receiveResp(CHIResponseType:Comp) == false) { | |
| error("Received unexpected message"); | |
| } | |
| } | |
| action(Receive_SnpResp, desc="") { | |
| assert(tbe.expected_snp_resp.hasExpected()); | |
| peek(rspInPort, CHIResponseMsg) { | |
| // Decrement pending | |
| if (tbe.expected_snp_resp.receiveResp(in_msg.type) == false) { | |
| error("Received unexpected message"); | |
| } | |
| assert(in_msg.stale == tbe.is_stale); | |
| } | |
| } | |
| action(Receive_RetryAck, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.pendReqAllowRetry); | |
| assert(tbe.rcvdRetryAck == false); | |
| tbe.rcvdRetryAck := true; | |
| destsWaitingRetry.addNetDest(tbe.pendReqDest); | |
| enqueueDoRetry(tbe); | |
| } | |
| action(Receive_PCrdGrant, desc="") { | |
| assert(tbe.pendReqAllowRetry); | |
| assert(tbe.rcvdRetryCredit == false); | |
| tbe.rcvdRetryCredit := true; | |
| enqueueDoRetry(tbe); | |
| } | |
| action(Send_Retry, desc="") { | |
| assert(tbe.pendReqAllowRetry); | |
| assert(tbe.rcvdRetryCredit); | |
| assert(tbe.rcvdRetryAck); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequestRetry(tbe, out_msg); | |
| } | |
| } | |
| action(Send_Retry_DVM, desc="") { | |
| assert(tbe.pendReqAllowRetry); | |
| assert(tbe.rcvdRetryCredit); | |
| assert(tbe.rcvdRetryAck); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequestRetryDVM(tbe, out_msg); | |
| } | |
| destsWaitingRetry.removeNetDest(tbe.pendReqDest); | |
| } | |
| action(Receive_RetryAck_Hazard, desc="") { | |
| TBE hazard_tbe := getHazardTBE(tbe); | |
| assert(hazard_tbe.pendReqAllowRetry); | |
| assert(hazard_tbe.rcvdRetryAck == false); | |
| hazard_tbe.rcvdRetryAck := true; | |
| destsWaitingRetry.addNetDest(hazard_tbe.pendReqDest); | |
| enqueueDoRetry(hazard_tbe); | |
| } | |
| action(Receive_PCrdGrant_Hazard, desc="") { | |
| TBE hazard_tbe := getHazardTBE(tbe); | |
| assert(hazard_tbe.pendReqAllowRetry); | |
| assert(hazard_tbe.rcvdRetryCredit == false); | |
| hazard_tbe.rcvdRetryCredit := true; | |
| enqueueDoRetry(hazard_tbe); | |
| } | |
| action(Send_Retry_Hazard, desc="") { | |
| TBE hazard_tbe := getHazardTBE(tbe); | |
| assert(hazard_tbe.pendReqAllowRetry); | |
| assert(hazard_tbe.rcvdRetryCredit); | |
| assert(hazard_tbe.rcvdRetryAck); | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequestRetry(hazard_tbe, out_msg); | |
| } | |
| } | |
| action(Send_CompData, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.dataValid); | |
| bool is_rd_once := tbe.reqType == CHIRequestType:ReadOnce; | |
| bool is_rd_shared := (tbe.reqType == CHIRequestType:ReadShared) || | |
| (tbe.reqType == CHIRequestType:ReadNotSharedDirty); | |
| bool is_rd_nsd := tbe.reqType == CHIRequestType:ReadNotSharedDirty; | |
| bool is_rd_unique := tbe.reqType == CHIRequestType:ReadUnique; | |
| // if the config allows (or not caching the data) and line has no sharers | |
| bool snd_unique_on_rs := (fwd_unique_on_readshared || tbe.dataToBeInvalid) | |
| && tbe.dataUnique && tbe.dir_sharers.isEmpty(); | |
| // if the request type allows and we won't be caching the data | |
| bool snd_dirty_on_rs := is_rd_shared && !is_rd_nsd && tbe.dataToBeInvalid; | |
| if (is_rd_once) { | |
| tbe.snd_msgType := CHIDataType:CompData_I; | |
| } else if (is_rd_unique || (is_rd_shared && snd_unique_on_rs)) { | |
| assert(tbe.dataUnique); | |
| if (tbe.dataDirty) { | |
| tbe.snd_msgType := CHIDataType:CompData_UD_PD; | |
| } else { | |
| tbe.snd_msgType := CHIDataType:CompData_UC; | |
| } | |
| } else if (is_rd_shared) { | |
| if (tbe.dataDirty && snd_dirty_on_rs) { | |
| tbe.snd_msgType := CHIDataType:CompData_SD_PD; | |
| } else { | |
| // notice the MaintainCoherence will send WriteClean if the line | |
| // is dirty and we won't be caching the data | |
| tbe.snd_msgType := CHIDataType:CompData_SC; | |
| } | |
| } else { | |
| error("Invalid request type"); | |
| } | |
| tbe.dataMaybeDirtyUpstream := tbe.dataMaybeDirtyUpstream || | |
| (tbe.snd_msgType == CHIDataType:CompData_UD_PD) || | |
| (tbe.snd_msgType == CHIDataType:CompData_SD_PD) || | |
| (tbe.snd_msgType == CHIDataType:CompData_UC); | |
| tbe.requestorToBeExclusiveOwner := tbe.requestorToBeExclusiveOwner || | |
| (tbe.snd_msgType == CHIDataType:CompData_UD_PD) || | |
| (tbe.snd_msgType == CHIDataType:CompData_UC); | |
| tbe.requestorToBeOwner := tbe.requestorToBeOwner || | |
| (tbe.snd_msgType == CHIDataType:CompData_SD_PD); | |
| tbe.snd_destination := tbe.requestor; | |
| setupPendingSend(tbe); | |
| printTBEState(tbe); | |
| } | |
| action(Send_WBData, desc="") { | |
| assert(is_valid(tbe)); | |
| if (is_HN) { | |
| assert(tbe.dataBlkValid.isFull()); | |
| assert(tbe.dataDirty); | |
| assert(tbe.dataValid); | |
| tbe.snd_msgType := CHIDataType:NCBWrData; | |
| } else { | |
| if (tbe.dataValid == false) { | |
| // only possible when the WB was made stale by a snoop | |
| assert(tbe.is_stale); | |
| tbe.dataBlkValid.fillMask(); | |
| tbe.snd_msgType := CHIDataType:CBWrData_I; | |
| } else if (tbe.dataUnique) { | |
| assert(tbe.dataBlkValid.isFull()); | |
| if (tbe.dataDirty) { | |
| tbe.snd_msgType := CHIDataType:CBWrData_UD_PD; | |
| } else { | |
| tbe.snd_msgType := CHIDataType:CBWrData_UC; | |
| } | |
| } else { | |
| assert(tbe.dataBlkValid.isFull()); | |
| if (tbe.dataDirty) { | |
| tbe.snd_msgType := CHIDataType:CBWrData_SD_PD; | |
| } else { | |
| tbe.snd_msgType := CHIDataType:CBWrData_SC; | |
| } | |
| } | |
| } | |
| tbe.snd_destination := mapAddressToDownstreamMachine(tbe.addr); | |
| setupPendingSend(tbe); | |
| } | |
| action(Send_WUData, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.dataBlkValid.count() > 0); | |
| tbe.snd_msgType := CHIDataType:NCBWrData; | |
| tbe.snd_destination := mapAddressToDownstreamMachine(tbe.addr); | |
| setupPendingPartialSend(tbe); | |
| } | |
| action(CheckWUComp, desc="") { | |
| assert(is_valid(tbe)); | |
| if (tbe.defer_expected_comp) { | |
| tbe.defer_expected_comp := false; | |
| tbe.expected_req_resp.addExpectedCount(1); | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:Comp); | |
| } | |
| } | |
| action(Send_SnpRespData, desc="") { | |
| assert(is_HN == false); | |
| assert(is_valid(tbe)); | |
| assert(tbe.dataBlkValid.isFull()); | |
| assert(tbe.dataValid); | |
| assert(tbe.snpNeedsData || | |
| (tbe.dataDirty && (tbe.reqType == CHIRequestType:SnpCleanInvalid)) || | |
| ((tbe.dataDirty || tbe.dataUnique) && (tbe.reqType == CHIRequestType:SnpShared)) || | |
| ((tbe.dataDirty || tbe.dataUnique) && (tbe.reqType == CHIRequestType:SnpUnique))); | |
| if (tbe.dataToBeInvalid) { | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| if (tbe.dataDirty) { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_I_PD; | |
| } else { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_I; | |
| } | |
| } else if (tbe.dataToBeSharedClean) { | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| if (tbe.dataDirty) { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_SC_PD; | |
| } else { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_SC; | |
| } | |
| } else { | |
| assert(tbe.reqType == CHIRequestType:SnpOnce); | |
| if (tbe.dataDirty && tbe.dataUnique) { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_UD; | |
| } else if (tbe.dataDirty) { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_SD; | |
| } else if (tbe.dataUnique) { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_UC; | |
| } else { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_SC; | |
| } | |
| } | |
| tbe.snd_destination := tbe.requestor; | |
| setupPendingSend(tbe); | |
| } | |
| action(Send_CompData_SnpUniqueFwd, desc="") { | |
| assert(tbe.dataValid); | |
| assert(tbe.dataToBeInvalid); | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| if (tbe.dataDirty) { | |
| tbe.fwdedState := State:UD; | |
| tbe.snd_msgType := CHIDataType:CompData_UD_PD; | |
| } else { | |
| tbe.fwdedState := State:UC; | |
| tbe.snd_msgType := CHIDataType:CompData_UC; | |
| } | |
| tbe.actions.pushFront(Event:SendSnpFwdedResp); | |
| tbe.snd_destination := tbe.fwdRequestor; | |
| setupPendingSend(tbe); | |
| } | |
| action(Send_CompData_SnpSharedFwd, desc="") { | |
| assert(tbe.dataValid); | |
| assert(tbe.dataToBeSharedClean); | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| if (tbe.dataDirty) { | |
| tbe.fwdedState := State:SD; | |
| tbe.snd_msgType := CHIDataType:CompData_SD_PD; | |
| } else { | |
| tbe.fwdedState := State:SC; | |
| tbe.snd_msgType := CHIDataType:CompData_SC; | |
| } | |
| if (tbe.snpNeedsData) { | |
| tbe.actions.pushFront(Event:SendSnpFwdedData); | |
| } else { | |
| tbe.actions.pushFront(Event:SendSnpFwdedResp); | |
| } | |
| tbe.snd_destination := tbe.fwdRequestor; | |
| setupPendingSend(tbe); | |
| } | |
| action(Send_CompData_SnpNSDFwd, desc="") { | |
| assert(tbe.dataValid); | |
| assert(tbe.dataToBeSharedClean); | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| tbe.snd_msgType := CHIDataType:CompData_SC; | |
| tbe.fwdedState := State:SC; | |
| if (tbe.dataDirty || tbe.snpNeedsData) { | |
| tbe.actions.pushFront(Event:SendSnpFwdedData); | |
| } else { | |
| tbe.actions.pushFront(Event:SendSnpFwdedResp); | |
| } | |
| tbe.snd_destination := tbe.fwdRequestor; | |
| setupPendingSend(tbe); | |
| } | |
| action(Send_CompData_SnpOnceFwd, desc="") { | |
| assert(tbe.dataValid); | |
| tbe.fwdedState := State:I; | |
| tbe.snd_msgType := CHIDataType:CompData_I; | |
| tbe.actions.pushFront(Event:SendSnpFwdedResp); | |
| tbe.snd_destination := tbe.fwdRequestor; | |
| setupPendingSend(tbe); | |
| } | |
| action(Send_SnpRespDataFwded, desc="") { | |
| assert(tbe.dataValid); | |
| // right only using this for the SnpShared/SnpNSD, so check | |
| assert(tbe.dataToBeSharedClean); | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| // We have the data (locally or upstream) or are dropping it | |
| bool keepData := (tbe.dir_sharers.count() > 0) || | |
| (tbe.dataToBeInvalid == false); | |
| if (keepData) { | |
| if (tbe.fwdedState == State:SD) { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_SC_Fwded_SD_PD; | |
| } else if (tbe.dataDirty && (tbe.fwdedState == State:SC)) { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_SC_PD_Fwded_SC; | |
| } else { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_SC_Fwded_SC; | |
| } | |
| } else { | |
| if (tbe.fwdedState == State:SD) { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_I_Fwded_SD_PD; | |
| } else if (tbe.dataDirty && (tbe.fwdedState == State:SC)) { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_I_PD_Fwded_SC; | |
| } else { | |
| tbe.snd_msgType := CHIDataType:SnpRespData_I_Fwded_SC; | |
| } | |
| } | |
| tbe.snd_destination := tbe.requestor; | |
| setupPendingSend(tbe); | |
| } | |
| action(Send_FwdSnpResp, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.dataValid); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| // We have the data (locally or upstream) or are dropping it | |
| bool keepData := (tbe.dir_sharers.count() > 0) || | |
| (tbe.dataToBeInvalid == false); | |
| if (keepData && tbe.dataToBeSharedClean) { | |
| assert((tbe.reqType == CHIRequestType:SnpSharedFwd) || | |
| (tbe.reqType == CHIRequestType:SnpNotSharedDirtyFwd)); | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| if (tbe.fwdedState == State:SD) { | |
| out_msg.type := CHIResponseType:SnpResp_SC_Fwded_SD_PD; | |
| } else { | |
| assert(tbe.fwdedState == State:SC); | |
| out_msg.type := CHIResponseType:SnpResp_SC_Fwded_SC; | |
| } | |
| } else if (keepData) { | |
| assert(tbe.reqType == CHIRequestType:SnpOnceFwd); | |
| assert(tbe.fwdedState == State:I); | |
| if (tbe.dataUnique && (tbe.dataDirty || tbe.dataMaybeDirtyUpstream)) { | |
| out_msg.type := CHIResponseType:SnpResp_UD_Fwded_I; | |
| } else if (tbe.dataUnique) { | |
| out_msg.type := CHIResponseType:SnpResp_UC_Fwded_I; | |
| } else if (tbe.dataDirty || tbe.dataMaybeDirtyUpstream) { | |
| out_msg.type := CHIResponseType:SnpResp_SD_Fwded_I; | |
| } else { | |
| out_msg.type := CHIResponseType:SnpResp_SC_Fwded_I; | |
| } | |
| } else { | |
| assert(tbe.reqType == CHIRequestType:SnpUniqueFwd); | |
| assert(tbe.dataMaybeDirtyUpstream == false); | |
| if (tbe.fwdedState == State:UD) { | |
| out_msg.type := CHIResponseType:SnpResp_I_Fwded_UD_PD; | |
| } else { | |
| assert(tbe.fwdedState == State:UC); | |
| out_msg.type := CHIResponseType:SnpResp_I_Fwded_UC; | |
| } | |
| } | |
| } | |
| } | |
| action(Send_Data, desc="") { | |
| assert(tbe.snd_pendEv); | |
| assert(tbe.snd_pendBytes.count() > 0); | |
| tbe.snd_pendEv := false; | |
| enqueue(datOutPort, CHIDataMsg, data_latency) { | |
| out_msg.addr := tbe.addr; | |
| out_msg.type := tbe.snd_msgType; | |
| int offset := tbe.snd_pendBytes.firstBitSet(true); | |
| assert(offset < blockSize); | |
| int range := tbe.snd_pendBytes.firstBitSet(false, offset) - offset; | |
| assert((range > 0) && (range <= blockSize)); | |
| if (range > data_channel_size) { | |
| range := data_channel_size; | |
| } | |
| tbe.snd_pendBytes.setMask(offset, range, false); | |
| out_msg.dataBlk := tbe.dataBlk; | |
| out_msg.bitMask.setMask(offset, range); | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.snd_destination); | |
| } | |
| // send next chunk (if any) next cycle | |
| scheduleSendData(tbe, 1); | |
| } | |
| action(Send_RespSepData, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:RespSepData; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| } | |
| } | |
| action(Send_CompI, desc="") { | |
| assert(is_valid(tbe)); | |
| // Used to ack Evict request | |
| assert(tbe.dir_sharers.isElement(tbe.requestor)); | |
| assert((tbe.dir_ownerExists == false) || (tbe.dir_owner != tbe.requestor)); | |
| tbe.dir_sharers.remove(tbe.requestor); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:Comp_I; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| } | |
| } | |
| action(Send_CompUC, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:Comp_UC; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| } | |
| } | |
| action(Send_CompUC_Stale, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:Comp_UC; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| // We don't know if this is a stale clean unique or a bug, so flag the | |
| // reponse so the requestor can make further checks | |
| out_msg.stale := true; | |
| } | |
| } | |
| action(Send_CompAck, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:CompAck; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(mapAddressToDownstreamMachine(tbe.addr)); | |
| } | |
| } | |
| action(Send_CompI_Stale, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:Comp_I; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| // We don't know if this is a stale writeback or a bug, so flag the | |
| // reponse so the requestor can make further checks | |
| out_msg.stale := true; | |
| } | |
| } | |
| action(Send_CompDBIDResp, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:CompDBIDResp; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| } | |
| } | |
| action(Send_CompDBIDResp_Stale, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:CompDBIDResp; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| // We don't know if this is a stale writeback or a bug, so flag the | |
| // reponse so the requestor can make further checks | |
| out_msg.stale := true; | |
| } | |
| } | |
| action(Send_DBIDResp, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:DBIDResp; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| } | |
| } | |
| action(Send_Comp_WU, desc="") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, comp_wu_latency + response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:Comp; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| } | |
| } | |
| action(Send_SnpRespI, desc="") { | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| if (tbe.is_dvm_tbe || tbe.is_dvm_snp_tbe) { | |
| out_msg.usesTxnId := true; | |
| out_msg.txnId := tbe.addr; | |
| } | |
| out_msg.type := CHIResponseType:SnpResp_I; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| } | |
| } | |
| action(Send_RetryAck, desc="") { | |
| peek(retryTriggerInPort, RetryTriggerMsg) { | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := in_msg.addr; | |
| out_msg.usesTxnId := in_msg.usesTxnId; | |
| out_msg.type := CHIResponseType:RetryAck; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(in_msg.retryDest); | |
| } | |
| } | |
| } | |
| action(Send_PCrdGrant, desc="") { | |
| peek(retryTriggerInPort, RetryTriggerMsg) { | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := in_msg.addr; | |
| out_msg.usesTxnId := in_msg.usesTxnId; | |
| out_msg.type := CHIResponseType:PCrdGrant; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(in_msg.retryDest); | |
| } | |
| } | |
| } | |
| // Note on CheckUpgrade_FromStoreOrRU/CheckUpgrade_FromCU | |
| // We will always get Comp_UC; but if our data is invalidated before | |
| // Comp_UC we would need to go to UCE. Since we don't use the UCE state | |
| // we remain in the transient state and follow-up with ReadUnique. | |
| // Note this assumes the responder knows we have invalid data when sending | |
| // us Comp_UC and does not register us as owner. | |
| action(CheckUpgrade_FromStoreOrRU, desc="") { | |
| assert(is_HN == false); | |
| if (tbe.dataUnique) { | |
| // success, just send CompAck next | |
| assert(tbe.dataValid); | |
| } else { | |
| // will need to get data instead | |
| tbe.actions.pushFront(Event:SendReadUnique); | |
| // we must have received an invalidation snoop that marked | |
| // the req as stale | |
| assert(tbe.is_stale); | |
| tbe.is_stale := false; | |
| } | |
| tbe.actions.pushFront(Event:SendCompAck); | |
| } | |
| action(CheckUpgrade_FromCU, desc="") { | |
| assert(is_HN == false); | |
| if (tbe.dataUnique == false) { | |
| // actually failed, so just cancel the directory update | |
| assert(tbe.dir_sharers.isElement(tbe.requestor) == false); | |
| tbe.requestorToBeExclusiveOwner := false; | |
| tbe.updateDirOnCompAck := false; | |
| } | |
| // otherwise nothing else to do here other than acking the CleanUnique | |
| tbe.actions.pushFront(Event:SendCompAck); | |
| } | |
| action(Finalize_UpdateCacheFromTBE, desc="") { | |
| assert(is_valid(tbe)); | |
| State final := tbe.finalState; | |
| if ((final == State:UD_RSC) || (final == State:SD_RSC) || (final == State:UC_RSC) || | |
| (final == State:SC_RSC) || (final == State:UD) || (final == State:UD_T) || | |
| (final == State:SD) || (final == State:UC) || (final == State:SC) || | |
| (final == State:UC_RU) || (final == State:UD_RU) || (final == State:UD_RSD) || | |
| (final == State:SD_RSD)) { | |
| assert(tbe.dataBlkValid.isFull()); | |
| assert(tbe.dataValid); | |
| assert(is_valid(cache_entry)); | |
| cache_entry.DataBlk := tbe.dataBlk; | |
| DPRINTF(RubySlicc, "Cached data %s pfb %s\n", tbe.dataBlk, cache_entry.HWPrefetched); | |
| } else { | |
| // make sure only deallocate the cache entry if data is invalid | |
| assert(tbe.dataValid == false); | |
| if (is_valid(cache_entry)) { | |
| cache.deallocate(address); | |
| unset_cache_entry(); | |
| } | |
| } | |
| } | |
| action(Finalize_UpdateDirectoryFromTBE, desc="") { | |
| assert(is_valid(tbe)); | |
| State final := tbe.finalState; | |
| if ((final == State:UD_RSC) || (final == State:SD_RSC) || (final == State:UC_RSC) || | |
| (final == State:SC_RSC) || (final == State:UC_RU) || (final == State:UD_RU) || | |
| (final == State:UD_RSD) || (final == State:SD_RSD) || (final == State:RU) || | |
| (final == State:RSC) || (final == State:RSD) || (final == State:RUSD) || | |
| (final == State:RUSC)) { | |
| DirEntry dir_entry := getDirEntry(address); | |
| assert(is_valid(dir_entry)); | |
| assert(tbe.dir_sharers.count() > 0); | |
| dir_entry.ownerExists := tbe.dir_ownerExists; | |
| dir_entry.ownerIsExcl := tbe.dir_ownerIsExcl; | |
| dir_entry.owner := tbe.dir_owner; | |
| dir_entry.sharers := tbe.dir_sharers; | |
| } else { | |
| assert((tbe.dir_ownerExists == false) && tbe.dir_sharers.isEmpty()); | |
| if(directory.isTagPresent(address)) { | |
| directory.deallocate(address); | |
| } | |
| } | |
| } | |
| action(Deallocate_CacheBlock, desc="") { | |
| assert(is_valid(cache_entry)); | |
| cache.deallocate(address); | |
| unset_cache_entry(); | |
| } | |
| action(Allocate_DirEntry, desc="") { | |
| assert(directory.isTagPresent(address) == false); | |
| directory.allocate(address); | |
| } | |
| action(Deallocate_DirEntry, desc="") { | |
| assert(directory.isTagPresent(address)); | |
| directory.deallocate(address); | |
| } | |
| action(CheckCacheFill, desc="") { | |
| assert(is_valid(tbe)); | |
| // only perform the write if we have valid data and need to write | |
| bool need_fill := tbe.dataValid && (tbe.dataToBeInvalid == false) && tbe.doCacheFill; | |
| bool execute_next := true; | |
| if (need_fill && is_valid(cache_entry)) { | |
| // can write | |
| tbe.actions.pushFront(Event:DataArrayWrite); | |
| tbe.actions.pushFront(Event:FillPipe); | |
| } else if (need_fill && cache.cacheAvail(address)) { | |
| // don't have a cache block, but there is space to allocate one | |
| set_cache_entry(cache.allocate(address, new CacheEntry)); | |
| tbe.actions.pushFront(Event:DataArrayWriteOnFill); | |
| tbe.actions.pushFront(Event:FillPipe); | |
| } else if (need_fill) { | |
| // performs a cache block replacement. CheckCacheFill executes again | |
| // after the replacement | |
| execute_next := false; | |
| // pick a victim to deallocate | |
| Addr victim_addr := cache.cacheProbe(address); | |
| CacheEntry victim_entry := getCacheEntry(victim_addr); | |
| assert(is_valid(victim_entry)); | |
| TBE victim_tbe := getCurrentActiveTBE(victim_addr); | |
| // The `is_valid(victim_entry)` condition here is to avoid an unused | |
| // variable error when compiling to gem5.fast. | |
| if (is_invalid(victim_tbe) && is_valid(victim_entry)) { | |
| DPRINTF(RubySlicc, "Eviction for %#x victim: %#x state=%s\n", | |
| address, victim_addr, victim_entry.state); | |
| enqueue(replTriggerOutPort, ReplacementMsg, 0) { | |
| out_msg.addr := victim_addr; | |
| out_msg.from_addr := address; | |
| if (unify_repl_TBEs) { | |
| out_msg.slot := tbe.storSlot; | |
| DPRINTF(RubySlicc, "Reusing slot %d\n", out_msg.slot); | |
| } | |
| } | |
| } else { | |
| DPRINTF(RubySlicc, "Eviction for %#x victim: %#x state=%s\n", | |
| address, victim_addr, victim_tbe.state); | |
| // just wait until the transaction finishes to try again | |
| victim_tbe.wakeup_pending_tgr := true; | |
| } | |
| // wait until we can deallocate the victim_addr | |
| stall_and_wait(triggerInPort, victim_addr); | |
| } | |
| // only do the usual Pop_TriggerQueue+ProcessNextState if we have a block | |
| if (execute_next) { | |
| triggerInPort.dequeue(clockEdge()); | |
| clearPendingAction(tbe); | |
| processNextState(address, tbe, cache_entry); | |
| } else { | |
| wakeupPendingSnps(tbe); // might have stalled snoops that can execute now | |
| } | |
| } | |
| action(Finalize_DeallocateRequest, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.actions.empty()); | |
| wakeupPendingReqs(tbe); | |
| wakeupPendingSnps(tbe); | |
| wakeupPendingTgrs(tbe); | |
| if (tbe.is_req_tbe) { | |
| deallocateReqTBE(tbe); | |
| processRetryQueue(); | |
| } else if (tbe.is_snp_tbe) { | |
| deallocateSnpTBE(tbe); | |
| } else { | |
| deallocateReplacementTBE(tbe); | |
| if (unify_repl_TBEs) { | |
| processRetryQueue(); | |
| } | |
| } | |
| unset_tbe(); | |
| incomingTransactionEnd(address, curTransitionNextState()); | |
| } | |
| action(Finalize_DeallocateDvmRequest, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.actions.empty()); | |
| wakeupPendingReqs(tbe); | |
| wakeupPendingSnps(tbe); | |
| wakeupPendingTgrs(tbe); | |
| // Don't call processRetryQueue() because DVM ops don't interact with the retry queue | |
| assert(tbe.is_dvm_tbe); | |
| deallocateDvmTBE(tbe); | |
| unset_tbe(); | |
| } | |
| action(Finalize_DeallocateDvmSnoop, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.actions.empty()); | |
| wakeupPendingReqs(tbe); | |
| wakeupPendingSnps(tbe); | |
| wakeupPendingTgrs(tbe); | |
| // Don't call processRetryQueue() because DVM ops don't interact with the retry queue | |
| assert(tbe.is_dvm_snp_tbe); | |
| deallocateDvmSnoopTBE(tbe); | |
| unset_tbe(); | |
| // Last argument = false, so it uses a "unique ID" rather than an address | |
| incomingTransactionEnd(address, curTransitionNextState(), false); | |
| } | |
| action(Pop_ReqRdyQueue, desc="") { | |
| reqRdyPort.dequeue(clockEdge()); | |
| } | |
| action(Pop_RespInQueue, desc="") { | |
| rspInPort.dequeue(clockEdge()); | |
| } | |
| action(Pop_SnoopRdyQueue, desc="") { | |
| snpRdyPort.dequeue(clockEdge()); | |
| } | |
| action(Pop_DataInQueue, desc="") { | |
| datInPort.dequeue(clockEdge()); | |
| } | |
| // NOTICE a trigger event may wakeup another stalled trigger event so | |
| // this is always called first in the transitions so we don't pop the | |
| // wrong message | |
| action(Pop_TriggerQueue, desc="") { | |
| triggerInPort.dequeue(clockEdge()); | |
| } | |
| action(Pop_ReplTriggerQueue, desc="") { | |
| replTriggerInPort.dequeue(clockEdge()); | |
| // wakeup the transaction that triggered this eviction | |
| wakeup_port(triggerInPort, address); | |
| } | |
| action(Pop_RetryTriggerQueue, desc="") { | |
| retryTriggerInPort.dequeue(clockEdge()); | |
| } | |
| action(Pop_SnpInPort, desc="") { | |
| snpInPort.dequeue(clockEdge()); | |
| } | |
| action(Pop_SeqInPort, desc="") { | |
| seqInPort.dequeue(clockEdge()); | |
| } | |
| action(ProcessNextState, desc="") { | |
| assert(is_valid(tbe)); | |
| processNextState(address, tbe, cache_entry); | |
| } | |
| action(ProcessNextState_ClearPending, desc="") { | |
| assert(is_valid(tbe)); | |
| clearPendingAction(tbe); | |
| processNextState(address, tbe, cache_entry); | |
| } | |
| action(Callback_LoadHit, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.reqType == CHIRequestType:Load); | |
| if (tbe.is_local_pf == false) { | |
| assert(tbe.dataValid); | |
| DPRINTF(RubySlicc, "Read data %s\n", tbe.dataBlk); | |
| sequencer.readCallback(tbe.addr, tbe.dataBlk, false); | |
| } | |
| } | |
| action(Callback_StoreHit, desc="") { | |
| assert(is_valid(tbe)); | |
| assert((tbe.reqType == CHIRequestType:StoreLine) || | |
| (tbe.reqType == CHIRequestType:Store)); | |
| if (tbe.is_local_pf == false) { | |
| assert(tbe.dataValid); | |
| DPRINTF(RubySlicc, "Write before %s\n", tbe.dataBlk); | |
| sequencer.writeCallback(tbe.addr, tbe.dataBlk, false); | |
| DPRINTF(RubySlicc, "Write after %s\n", tbe.dataBlk); | |
| tbe.dataDirty := true; | |
| } | |
| } | |
| action(Callback_ExpressPrefetchHit, desc="") { | |
| // have not allocated TBE, but must clear the reservation | |
| assert(is_invalid(tbe)); | |
| storTBEs.decrementReserved(); | |
| assert(storTBEs.areNSlotsAvailable(1)); | |
| assert(use_prefetcher); | |
| cache.profilePrefetchHit(); | |
| peek(reqRdyPort, CHIRequestMsg) { | |
| assert(in_msg.is_local_pf); | |
| notifyPfComplete(in_msg.addr); | |
| } | |
| } | |
| // This is called everytime a data message is received but only goes | |
| // though once all the blocks are present (tbe.dataValid) | |
| // NOTE: should create a separate trigger for this callback ? | |
| action(Callback_Miss, desc="") { | |
| assert(is_valid(tbe)); | |
| if (tbe.dataValid && tbe.is_local_pf) { | |
| assert(use_prefetcher); | |
| notifyPfComplete(tbe.addr); | |
| } else if (tbe.dataValid && (tbe.reqType == CHIRequestType:Load)) { | |
| DPRINTF(RubySlicc, "Read data %s\n", tbe.dataBlk); | |
| sequencer.readCallback(tbe.addr, tbe.dataBlk, true); | |
| } else if (tbe.dataValid && ((tbe.reqType == CHIRequestType:Store) || | |
| (tbe.reqType == CHIRequestType:StoreLine))) { | |
| DPRINTF(RubySlicc, "Write before %s\n", tbe.dataBlk); | |
| sequencer.writeCallback(tbe.addr, tbe.dataBlk, true); | |
| DPRINTF(RubySlicc, "Write after %s\n", tbe.dataBlk); | |
| tbe.dataDirty := true; | |
| // sets a use time out for store misses to prevent LL/SC livelocks | |
| int use_timeout_latency := scLockLatency(); | |
| if (use_timeout_latency > 0) { | |
| if (tbe.hasUseTimeout) { | |
| assert(useTimerTable.isSet(tbe.addr)); | |
| } else { | |
| useTimerTable.set( | |
| tbe.addr, | |
| clockEdge() + cyclesToTicks(intToCycles(use_timeout_latency))); | |
| tbe.hasUseTimeout := true; | |
| } | |
| // also decay the timeout | |
| scLockDecayLatency(); | |
| } | |
| } | |
| } | |
| action(Unset_Timeout_TBE, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.hasUseTimeout); | |
| assert(useTimerTable.isSet(tbe.addr)); | |
| useTimerTable.unset(tbe.addr); | |
| tbe.hasUseTimeout := false; | |
| // A snoop may have been stalled without setting the TBE flag | |
| wakeup_port(snpRdyPort, address); | |
| } | |
| action(Unset_Timeout_Cache, desc="") { | |
| assert(useTimerTable.isSet(address)); | |
| useTimerTable.unset(address); | |
| wakeup_port(snpRdyPort, address); | |
| } | |
| action(Callback_WriteUnique, desc="") { | |
| assert(is_valid(tbe)); | |
| assert((tbe.is_local_pf || tbe.is_remote_pf) == false); | |
| assert((tbe.reqType == CHIRequestType:StoreLine) || | |
| (tbe.reqType == CHIRequestType:Store)); | |
| assert(tbe.dataValid == false); | |
| sequencer.writeUniqueCallback(tbe.addr, tbe.dataBlk); | |
| DPRINTF(RubySlicc, "WriteUnique data %s\n", tbe.dataBlk); | |
| // set mask; note data is never considered valid | |
| assert(tbe.dataBlkValid.isEmpty()); | |
| tbe.dataBlkValid.setMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize); | |
| } | |
| action(Profile_Miss, desc="") { | |
| assert(is_valid(tbe)); | |
| bool is_demand := (tbe.is_local_pf || tbe.is_remote_pf) == false; | |
| bool is_remote_can_notify := tbe.is_remote_pf && upstream_prefetch_trains_prefetcher; | |
| if (is_demand) { | |
| cache.profileDemandMiss(); | |
| } else { | |
| assert(use_prefetcher || tbe.is_remote_pf); | |
| cache.profilePrefetchMiss(); | |
| } | |
| // notify prefetcher about this demand miss | |
| if (use_prefetcher && tbe.isSeqReqValid && (is_demand || is_remote_can_notify)) { | |
| bool is_read := false; | |
| if (isReadReqType(tbe.reqType)) { | |
| is_read := true; | |
| } else { | |
| assert(isWriteReqType(tbe.reqType)); | |
| } | |
| // FIXME: this dataBlk is likely to have stale data. This should be fixed | |
| // if our prefetcher uses cached data to make prefetch decisions. | |
| notifyPfMiss(tbe.seqReq, is_read, tbe.dataBlk); | |
| } | |
| } | |
| action(Profile_Hit, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(is_valid(cache_entry)); | |
| assert(tbe.dataValid); | |
| bool is_demand := (tbe.is_local_pf || tbe.is_remote_pf) == false; | |
| bool is_remote_can_notify := tbe.is_remote_pf && upstream_prefetch_trains_prefetcher; | |
| if (is_demand) { | |
| cache.profileDemandHit(); | |
| } else { | |
| assert(use_prefetcher || tbe.is_remote_pf); | |
| cache.profilePrefetchHit(); | |
| } | |
| // notify prefetcher about this demand hit | |
| if (use_prefetcher && tbe.isSeqReqValid && (is_demand || is_remote_can_notify)) { | |
| bool is_read := false; | |
| if (isReadReqType(tbe.reqType)) { | |
| is_read := true; | |
| } else { | |
| assert(isWriteReqType(tbe.reqType)); | |
| } | |
| notifyPfHit(tbe.seqReq, is_read, tbe.dataBlk); | |
| cache_entry.HWPrefetched := false; | |
| } | |
| } | |
| action(Profile_Fill, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(is_valid(cache_entry)); | |
| if (use_prefetcher && tbe.isSeqReqValid) { | |
| cache_entry.HWPrefetched := tbe.is_local_pf || | |
| (tbe.is_remote_pf && | |
| (upstream_prefetch_trains_prefetcher == false)); | |
| // Prefetchers that use this info require notifications from both | |
| // demand and pf fills (unlike notifyPfHit/notifyPfMiss) | |
| notifyPfFill(tbe.seqReq, tbe.dataBlk, tbe.is_local_pf); | |
| } | |
| } | |
| action(Profile_Eviction, desc="") { | |
| if (sc_lock_enabled && sequencer.llscCheckMonitor(address)) { | |
| DPRINTF(LLSC, "Invalidating monitored address %#x\n", address); | |
| scLockIncLatency(); | |
| } | |
| if (send_evictions) { | |
| DPRINTF(RubySlicc, "Sending invalidation for %#x to the sequencer\n", address); | |
| sequencer.evictionCallback(address); | |
| } | |
| if (use_prefetcher && is_valid(cache_entry)) { | |
| notifyPfEvict(address, cache_entry.HWPrefetched); | |
| } | |
| } | |
| action(Profile_OutgoingStart, desc="") { | |
| outgoingTransactionStart(address, curTransitionEvent()); | |
| } | |
| action(Profile_OutgoingEnd_DataResp, desc="") { | |
| assert(is_valid(tbe)); | |
| // completes once all data is received | |
| if (tbe.expected_req_resp.hasReceivedData()) { | |
| outgoingTransactionEnd(address, tbe.rcvdRetryAck); | |
| } | |
| } | |
| action(Profile_OutgoingEnd_DatalessResp, desc="") { | |
| assert(is_valid(tbe)); | |
| outgoingTransactionEnd(address, tbe.rcvdRetryAck); | |
| } | |
| action(TagArrayRead, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks( | |
| tagLatency((tbe.reqType == CHIRequestType:Load) || | |
| (tbe.reqType == CHIRequestType:Store) || | |
| (tbe.reqType == CHIRequestType:StoreLine))); | |
| } | |
| action(TagArrayWrite, desc="") { | |
| assert(is_valid(tbe)); | |
| // when hasUseTimeout is set the final state is UD_T, but adding a delay | |
| // between now and triggering Fin_UD_T may allow the timer to expire and then | |
| // we end up in the wrong state | |
| if (dealloc_wait_for_tag && (tbe.hasUseTimeout == false)) { | |
| tbe.delayNextAction := curTick() + cyclesToTicks(tagLatency(false)); | |
| } | |
| } | |
| action(DataArrayRead, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks(dataLatency()); | |
| } | |
| action(DataArrayWrite, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(is_valid(cache_entry)); | |
| assert(tbe.doCacheFill); | |
| if(wait_for_cache_wr) { | |
| tbe.delayNextAction := curTick() + cyclesToTicks(dataLatency()); | |
| } | |
| } | |
| action(ReadHitPipe, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks(read_hit_latency); | |
| } | |
| action(ReadMissPipe, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks(read_miss_latency); | |
| } | |
| action(WriteFEPipe, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks(write_fe_latency); | |
| } | |
| action(WriteBEPipe, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks(write_be_latency); | |
| } | |
| action(FillPipe, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks(fill_latency); | |
| } | |
| action(SnpSharedPipe, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks(snp_latency); | |
| } | |
| action(SnpInvPipe, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks(snp_latency + snp_inv_latency); | |
| } | |
| action(SnpOncePipe, desc="") { | |
| assert(is_valid(tbe)); | |
| tbe.delayNextAction := curTick() + cyclesToTicks(snp_latency); | |
| } | |
| ////////////////////////////////// | |
| // DVM Actions | |
| action(Send_DvmTlbi, desc="") { | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:DvmOpNonSync, out_msg); | |
| DPRINTF(RubyProtocol, "Sending DvmOpNonSync to %d\n", getMiscNodeMachine()); | |
| out_msg.usesTxnId := true; | |
| out_msg.txnId := tbe.addr; // for DVM TBEs addr = txnId | |
| out_msg.Destination.clear(); | |
| out_msg.Destination.add(getMiscNodeMachine()); | |
| out_msg.dataToFwdRequestor := false; | |
| // Don't set message size, we don't use the data inside the messages | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| // TLBIs can be ended early if the MN chooses to send CompDBIDResp. | |
| // Otherwise, the MN sends a plain DBIDResp, and then sends a Comp later. | |
| // => We add two possible response types, then add 1 to the count | |
| // e.g. "expect exactly 1 (CompDBIDResp OR DBIDResp)" | |
| clearExpectedReqResp(tbe); | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:CompDBIDResp); | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:DBIDResp); | |
| tbe.expected_req_resp.addExpectedCount(1); | |
| // If a plain DBIDResp is recieved, then Comp will be manually expected. | |
| // (expect_sep_wu_comp also sort of handles this, but it's WU specific, | |
| // and ProcessNextState doesn't respect it). | |
| // Push a value to the list of pending NonSyncs | |
| // The actual value doesn't matter, but we have to pick | |
| // a type which already has function signatures | |
| // e.g. TriggerQueue has push(Event) specified in SLICC but not push(addr) | |
| DPRINTF(RubyProtocol, "Pushing pending nonsync to blocklist %16x\n", tbe.addr); | |
| dvmPendingNonSyncsBlockingSync.push(Event:DvmTlbi_Initiate); | |
| } | |
| // Try to send a DVM Sync, but put it in the pending slot | |
| // if there are pending Non-Syncs blocking it. | |
| action(Try_Send_DvmSync, desc="") { | |
| if (dvmPendingNonSyncsBlockingSync.empty()){ | |
| DPRINTF(RubyProtocol, "Nonsync queue is empty so %016x can proceed\n", tbe.addr); | |
| tbe.actions.push(Event:DvmSync_Send); | |
| } else { | |
| assert(!dvmHasPendingSyncOp); | |
| DPRINTF(RubyProtocol, "Nonsync queue is not empty so %016x is now pending\n", tbe.addr); | |
| dvmHasPendingSyncOp := true; | |
| dvmPendingSyncOp := address; | |
| } | |
| } | |
| // Try to send a DVM sync that was put in the pending slot | |
| // due to pending Non-Syncs blocking it. Those Non-Syncs may not be | |
| // blocking it anymore. | |
| action(Try_Send_Pending_DvmSync, desc="") { | |
| // Pop an element off the list of pending NonSyncs | |
| // It won't necessarily be ours, but that doesn't matter. | |
| assert(!dvmPendingNonSyncsBlockingSync.empty()); | |
| DPRINTF(RubyProtocol, "Popping nonsync from blocklist %16x\n", tbe.addr); | |
| dvmPendingNonSyncsBlockingSync.pop(); | |
| if (dvmPendingNonSyncsBlockingSync.empty() && dvmHasPendingSyncOp) { | |
| DPRINTF(RubyProtocol, "Blocklist now empty, pending op %16x can proceed\n", dvmPendingSyncOp); | |
| TBE syncTBE := getDvmTBE(dvmPendingSyncOp); | |
| assert(is_valid(syncTBE)); | |
| syncTBE.actions.push(Event:DvmSync_Send); | |
| dvmHasPendingSyncOp := false; | |
| } | |
| } | |
| action(Send_DvmSync, desc="") { | |
| enqueue(reqOutPort, CHIRequestMsg, request_latency) { | |
| prepareRequest(tbe, CHIRequestType:DvmOpSync, out_msg); | |
| DPRINTF(RubyProtocol, "Sending DvmOpSync to %d\n", getMiscNodeMachine()); | |
| out_msg.usesTxnId := true; | |
| out_msg.txnId := tbe.addr; // for DVM TBEs addr = txnId | |
| out_msg.Destination.clear(); | |
| out_msg.Destination.add(getMiscNodeMachine()); | |
| out_msg.dataToFwdRequestor := false; | |
| // Don't set message size, we don't use the data inside the messages | |
| allowRequestRetry(tbe, out_msg); | |
| } | |
| clearExpectedReqResp(tbe); | |
| tbe.expected_req_resp.addExpectedRespType(CHIResponseType:DBIDResp); | |
| tbe.expected_req_resp.addExpectedCount(1); | |
| // Comp will be expected later | |
| } | |
| action(Send_DvmTlbi_NCBWrData, desc="") { | |
| enqueue(datOutPort, CHIDataMsg, data_latency) { | |
| out_msg.addr := tbe.addr; | |
| out_msg.type := CHIDataType:NCBWrData; | |
| out_msg.usesTxnId := true; | |
| out_msg.txnId := tbe.addr; // for DVM TBEs addr = txnId | |
| // Set dataBlk to all 0 - we don't actually use the contents | |
| out_msg.dataBlk.clear(); | |
| // Data should be 8 bytes - this function is (offset, range) | |
| out_msg.bitMask.setMask(0, 8); | |
| out_msg.responder := machineID; | |
| out_msg.Destination.clear(); | |
| out_msg.Destination.add(getMiscNodeMachine()); | |
| } | |
| } | |
| action(Send_DvmSync_NCBWrData, desc="") { | |
| enqueue(datOutPort, CHIDataMsg, data_latency) { | |
| out_msg.addr := tbe.addr; | |
| out_msg.type := CHIDataType:NCBWrData; | |
| out_msg.usesTxnId := true; | |
| out_msg.txnId := tbe.addr; // for DVM TBEs addr = txnId | |
| // Set dataBlk to all 0 - we don't actually use the contents | |
| out_msg.dataBlk.clear(); | |
| // Data should be 8 bytes - this function is (offset, range) | |
| // I assume the range is in bytes... | |
| out_msg.bitMask.setMask(0, 8); | |
| out_msg.responder := machineID; | |
| out_msg.Destination.clear(); | |
| out_msg.Destination.add(getMiscNodeMachine()); | |
| } | |
| } | |
| action(DvmTlbi_CompCallback, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.is_dvm_tbe); | |
| assert(tbe.reqType == CHIRequestType:DvmTlbi_Initiate); | |
| sequencer.unaddressedCallback(tbe.addr, RubyRequestType:TLBI); | |
| } | |
| action(DvmSync_CompCallback, desc="") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.is_dvm_tbe); | |
| assert(tbe.reqType == CHIRequestType:DvmSync_Initiate); | |
| sequencer.unaddressedCallback(tbe.addr, RubyRequestType:TLBI_SYNC); | |
| } | |
| ////////////////////////////////// | |
| // DVM Snoop Actions | |
| action(Initiate_DvmSnoop, desc="") { | |
| // DvmSnoop cannot be retried | |
| bool was_retried := false; | |
| peek(snpRdyPort, CHIRequestMsg) { | |
| set_tbe(allocateDvmSnoopTBE(address, in_msg)); | |
| } | |
| // Last argument = false, so it uses a "unique ID" rather than an address | |
| // "Incoming" transactions for DVM = time between receiving a Snooped DVM op | |
| // and sending the SnpResp_I | |
| incomingTransactionStart(address, curTransitionEvent(), State:I, was_retried, false); | |
| } | |
| action(DvmExtTlbi_EnqueueSnpResp, desc=""){ | |
| tbe.delayNextAction := curTick() + cyclesToTicks(dvm_ext_tlbi_latency); | |
| tbe.actions.push(Event:SendSnpIResp); | |
| } | |
| action(DvmExtSync_TriggerCallback, desc=""){ | |
| assert(is_valid(tbe)); | |
| assert(tbe.is_dvm_snp_tbe); | |
| sequencer.unaddressedCallback(tbe.addr, RubyRequestType:TLBI_EXT_SYNC); | |
| } | |
| action(Profile_OutgoingStart_DVM, desc="") { | |
| outgoingTransactionStart(address, curTransitionEvent(), false); | |
| } | |
| action(Profile_OutgoingEnd_DVM, desc="") { | |
| assert(is_valid(tbe)); | |
| outgoingTransactionEnd(address, tbe.rcvdRetryAck, false); | |
| } |