| /* | |
| * 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 function definitions | |
| //////////////////////////////////////////////////////////////////////////// | |
| //////////////////////////////////////////////////////////////////////////// | |
| // External functions | |
| Tick clockEdge(); | |
| Tick curTick(); | |
| Tick cyclesToTicks(Cycles c); | |
| Cycles ticksToCycles(Tick t); | |
| void set_tbe(TBE b); | |
| void unset_tbe(); | |
| MachineID mapAddressToDownstreamMachine(Addr addr); | |
| NetDest allUpstreamDest(); | |
| void incomingTransactionStart(Addr, Event, State, bool); | |
| void incomingTransactionEnd(Addr, State); | |
| void outgoingTransactionStart(Addr, Event); | |
| void outgoingTransactionEnd(Addr, bool); | |
| // Overloads for transaction-measuring functions | |
| // final bool = isMemoryAccess | |
| // if false, uses a "global access" table | |
| void incomingTransactionStart(Addr, Event, State, bool, bool); | |
| void incomingTransactionEnd(Addr, State, bool); | |
| void outgoingTransactionStart(Addr, Event, bool); | |
| void outgoingTransactionEnd(Addr, bool, bool); | |
| Event curTransitionEvent(); | |
| State curTransitionNextState(); | |
| void notifyPfHit(RequestPtr req, bool is_read, DataBlock blk) { } | |
| void notifyPfMiss(RequestPtr req, bool is_read, DataBlock blk) { } | |
| void notifyPfFill(RequestPtr req, DataBlock blk, bool from_pf) { } | |
| void notifyPfEvict(Addr blkAddr, bool hwPrefetched) { } | |
| void notifyPfComplete(Addr addr) { } | |
| void scheduleEvent(Cycles); | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Interface functions required by SLICC | |
| int tbePartition(bool is_non_sync) { | |
| if (is_non_sync) { | |
| return 1; | |
| } else { | |
| return 0; | |
| } | |
| } | |
| State getState(TBE tbe, Addr txnId) { | |
| if (is_valid(tbe)) { | |
| return tbe.state; | |
| } else { | |
| return State:Unallocated; | |
| } | |
| } | |
| void setState(TBE tbe, Addr txnId, State state) { | |
| if (is_valid(tbe)) { | |
| tbe.state := state; | |
| } | |
| } | |
| TBE nullTBE(), return_by_pointer="yes" { | |
| return OOD; | |
| } | |
| TBE getCurrentActiveTBE(Addr txnId), return_by_pointer="yes" { | |
| // Current Active TBE for an address | |
| return dvmTBEs[txnId]; | |
| } | |
| AccessPermission getAccessPermission(Addr txnId) { | |
| // MN has no memory | |
| return AccessPermission:NotPresent; | |
| } | |
| void setAccessPermission(Addr txnId, State state) {} | |
| void functionalRead(Addr txnId, Packet *pkt, WriteMask &mask) { | |
| // We don't have any memory, so we can't functionalRead | |
| // => we don't fill the `mask` argument | |
| } | |
| int functionalWrite(Addr txnId, Packet *pkt) { | |
| // No memory => no functional writes | |
| return 0; | |
| } | |
| Cycles mandatoryQueueLatency(RubyRequestType type) { | |
| return intToCycles(1); | |
| } | |
| Cycles tagLatency(bool from_sequencer) { | |
| return intToCycles(0); | |
| } | |
| Cycles dataLatency() { | |
| return intToCycles(0); | |
| } | |
| bool inCache(Addr txnId) { | |
| return false; | |
| } | |
| bool hasBeenPrefetched(Addr txnId) { | |
| return false; | |
| } | |
| bool inMissQueue(Addr txnId) { | |
| return false; | |
| } | |
| void notifyCoalesced(Addr txnId, RubyRequestType type, RequestPtr req, | |
| DataBlock data_blk, bool was_miss) { | |
| DPRINTF(RubySlicc, "Unused notifyCoalesced(txnId=%#x, type=%s, was_miss=%d)\n", | |
| txnId, type, was_miss); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // State->Event converters | |
| Event reqToEvent(CHIRequestType type) { | |
| if (type == CHIRequestType:DvmOpNonSync) { | |
| return Event:DvmTlbi_Initiate; | |
| } else if (type == CHIRequestType:DvmOpSync) { | |
| return Event:DvmSync_Initiate; | |
| } else { | |
| error("Invalid/unexpected CHIRequestType"); | |
| } | |
| } | |
| Event respToEvent (CHIResponseType type) { | |
| if (type == CHIResponseType:SnpResp_I) { | |
| return Event:SnpResp_I; | |
| } else { | |
| error("Invalid/unexpected CHIResponseType"); | |
| } | |
| } | |
| Event dataToEvent (CHIDataType type) { | |
| if (type == CHIDataType:NCBWrData) { | |
| return Event:NCBWrData; | |
| } else { | |
| error("Invalid/unexpected CHIDataType"); | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Allocation | |
| void clearExpectedReqResp(TBE tbe) { | |
| assert(blockSize >= data_channel_size); | |
| assert((blockSize % data_channel_size) == 0); | |
| tbe.expected_req_resp.clear(blockSize / data_channel_size); | |
| } | |
| void clearExpectedSnpResp(TBE tbe) { | |
| assert(blockSize >= data_channel_size); | |
| assert((blockSize % data_channel_size) == 0); | |
| tbe.expected_snp_resp.clear(blockSize / data_channel_size); | |
| } | |
| void initializeTBE(TBE tbe, Addr txnId, int storSlot) { | |
| assert(is_valid(tbe)); | |
| tbe.timestamp := curTick(); | |
| tbe.wakeup_pending_req := false; | |
| tbe.wakeup_pending_snp := false; | |
| tbe.wakeup_pending_tgr := false; | |
| tbe.txnId := txnId; | |
| tbe.storSlot := storSlot; | |
| clearExpectedReqResp(tbe); | |
| clearExpectedSnpResp(tbe); | |
| // Technically we don't *know* if we're waiting on other transactions, | |
| // but we need to stop this transaction from errantly being *finished*. | |
| tbe.waiting_on_other_txns := true; | |
| tbe.sched_responses := 0; | |
| tbe.block_on_sched_responses := false; | |
| tbe.pendAction := Event:null; | |
| tbe.finalState := State:null; | |
| tbe.delayNextAction := intToTick(0); | |
| // The MN uses the list of "upstream destinations" | |
| // as targets for snoops | |
| tbe.notSentTargets := allUpstreamDest(); | |
| tbe.pendingTargets.clear(); | |
| tbe.receivedTargets.clear(); | |
| } | |
| TBE allocateDvmRequestTBE(Addr txnId, CHIRequestMsg in_msg), return_by_pointer="yes" { | |
| DPRINTF(RubySlicc, "allocateDvmRequestTBE %x %016llx\n", in_msg.type, txnId); | |
| bool isNonSync := in_msg.type == CHIRequestType:DvmOpNonSync; | |
| int partition := tbePartition(isNonSync); | |
| // We must have reserved resources for this allocation | |
| storDvmTBEs.decrementReserved(partition); | |
| assert(storDvmTBEs.areNSlotsAvailable(1, partition)); | |
| dvmTBEs.allocate(txnId); | |
| TBE tbe := dvmTBEs[txnId]; | |
| // Setting .txnId = txnId | |
| initializeTBE(tbe, txnId, storDvmTBEs.addEntryToNewSlot(partition)); | |
| tbe.isNonSync := isNonSync; | |
| tbe.requestor := in_msg.requestor; | |
| tbe.reqType := in_msg.type; | |
| // We don't want to send a snoop request to | |
| // the original requestor | |
| tbe.notSentTargets.remove(in_msg.requestor); | |
| return tbe; | |
| } | |
| void deallocateDvmTBE(TBE tbe) { | |
| assert(is_valid(tbe)); | |
| storDvmTBEs.removeEntryFromSlot(tbe.storSlot, tbePartition(tbe.isNonSync)); | |
| dvmTBEs.deallocate(tbe.txnId); | |
| } | |
| void clearPendingAction(TBE tbe) { | |
| tbe.pendAction := Event:null; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Retry-related | |
| void processRetryQueue() { | |
| // send credit if requestor waiting for it and we have resources | |
| // Ask the DVM storage if we have space to retry anything. | |
| if (storDvmTBEs.hasPossibleRetry()) { | |
| RetryQueueEntry toRetry := storDvmTBEs.popNextRetryEntry(); | |
| storDvmTBEs.incrementReserved(tbePartition(toRetry.isNonSync)); | |
| enqueue(retryTriggerOutPort, RetryTriggerMsg, crd_grant_latency) { | |
| out_msg.txnId := toRetry.txnId; | |
| out_msg.retryDest := toRetry.retryDest; | |
| out_msg.event := Event:SendPCrdGrant; | |
| } | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Other | |
| void printResources() { | |
| DPRINTF(RubySlicc, "Resources(used/rsvd/max): dvmTBEs=%d/%d/%d\n", | |
| storDvmTBEs.size(), storDvmTBEs.reserved(), storDvmTBEs.capacity()); | |
| DPRINTF(RubySlicc, "Resources(in/out size): req=%d/%d rsp=%d/%d dat=%d/%d snp=%d/%d trigger=%d\n", | |
| reqIn.getSize(curTick()), reqOut.getSize(curTick()), | |
| rspIn.getSize(curTick()), rspOut.getSize(curTick()), | |
| datIn.getSize(curTick()), datOut.getSize(curTick()), | |
| snpIn.getSize(curTick()), snpOut.getSize(curTick()), | |
| triggerQueue.getSize(curTick())); | |
| } | |
| void printTBEState(TBE tbe) { | |
| DPRINTF(RubySlicc, "STATE: txnId=%#x reqType=%d state=%d pendAction=%s\n", | |
| tbe.txnId, tbe.reqType, tbe.state, tbe.pendAction); | |
| } | |
| void prepareRequest(TBE tbe, CHIRequestType type, CHIRequestMsg & out_msg) { | |
| out_msg.addr := tbe.txnId; | |
| out_msg.accAddr := tbe.txnId; | |
| out_msg.accSize := blockSize; | |
| out_msg.requestor := machineID; | |
| out_msg.fwdRequestor := tbe.requestor; | |
| out_msg.type := type; | |
| out_msg.allowRetry := false; | |
| out_msg.isSeqReqValid := false; | |
| out_msg.is_local_pf := false; | |
| out_msg.is_remote_pf := false; | |
| } |