| /* | |
| * Copyright (c) 2021 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. | |
| */ | |
| machine(MachineType:Memory, "Memory controller interface") : | |
| // no explicit modeling of allocation latency like the Caches, so add one | |
| // cycle to the response enqueue latency as default | |
| Cycles response_latency := 2; | |
| Cycles data_latency := 1; | |
| Cycles to_memory_controller_latency := 1; | |
| int data_channel_size; | |
| // Interface to the network | |
| // Note vnet_type is used by Garnet only. "response" type is assumed to | |
| // have data, so use it for data channels and "none" for the rest. | |
| // network="To" for outbound queue; network="From" for inbound | |
| // virtual networks: 0=request, 1=snoop, 2=response, 3=data | |
| MessageBuffer * reqOut, network="To", virtual_network="0", vnet_type="none"; | |
| MessageBuffer * snpOut, network="To", virtual_network="1", vnet_type="none"; | |
| MessageBuffer * rspOut, network="To", virtual_network="2", vnet_type="none"; | |
| MessageBuffer * datOut, network="To", virtual_network="3", vnet_type="response"; | |
| MessageBuffer * reqIn, network="From", virtual_network="0", vnet_type="none"; | |
| MessageBuffer * snpIn, network="From", virtual_network="1", vnet_type="none"; | |
| MessageBuffer * rspIn, network="From", virtual_network="2", vnet_type="none"; | |
| MessageBuffer * datIn, network="From", virtual_network="3", vnet_type="response"; | |
| // Requests that can allocate a TBE | |
| MessageBuffer * reqRdy; | |
| // Data/ack to/from memory | |
| MessageBuffer * requestToMemory; | |
| MessageBuffer * responseFromMemory; | |
| // Trigger queue for internal events | |
| MessageBuffer * triggerQueue; | |
| { | |
| //////////////////////////////////////////////////////////////////////////// | |
| // States | |
| //////////////////////////////////////////////////////////////////////////// | |
| state_declaration(State, desc="Transaction states", default="Memory_State_READY") { | |
| // We don't know if the line is cached, so the memory copy is maybe stable | |
| READY, AccessPermission:Backing_Store, desc="Ready to transfer the line"; | |
| WAITING_NET_DATA, AccessPermission:Backing_Store_Busy, desc="Waiting data from the network"; | |
| SENDING_NET_DATA, AccessPermission:Backing_Store_Busy, desc="Sending data to the network"; | |
| READING_MEM, AccessPermission:Backing_Store_Busy, desc="Waiting data from memory"; | |
| // Null state for debugging; allow writes | |
| null, AccessPermission:Backing_Store, desc="Null state"; | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Events | |
| //////////////////////////////////////////////////////////////////////////// | |
| enumeration(Event, desc="Memory events") { | |
| // Checks if a request can allocate a TBE be moved to reqRdy | |
| CheckAllocTBE, desc=""; | |
| CheckAllocTBE_WithCredit, desc=""; | |
| // Requests | |
| WriteNoSnpPtl, desc=""; | |
| WriteNoSnp, desc=""; | |
| ReadNoSnp, desc=""; | |
| ReadNoSnpSep, desc=""; | |
| // Data | |
| WriteData, desc=""; | |
| // Memory side | |
| MemoryData, desc=""; | |
| MemoryAck, desc=""; | |
| // Internal event triggers | |
| Trigger_Send, desc=""; | |
| Trigger_SendDone, desc=""; | |
| Trigger_ReceiveDone, desc=""; | |
| Trigger_SendRetry, desc=""; | |
| Trigger_SendPCrdGrant, desc=""; | |
| } | |
| // Is there a less tedious way to convert messages to events ?? | |
| Event reqToEvent (CHIRequestType type) { | |
| if (type == CHIRequestType:WriteNoSnpPtl) { | |
| return Event:WriteNoSnpPtl; | |
| } else if (type == CHIRequestType:WriteNoSnp) { | |
| return Event:WriteNoSnp; | |
| } else if (type == CHIRequestType:ReadNoSnp) { | |
| return Event:ReadNoSnp; | |
| } else if (type == CHIRequestType:ReadNoSnpSep) { | |
| return Event:ReadNoSnpSep; | |
| } else { | |
| error("Invalid CHIRequestType"); | |
| } | |
| } | |
| Event respToEvent (CHIResponseType type) { | |
| error("Invalid CHIResponseType"); | |
| } | |
| Event dataToEvent (CHIDataType type) { | |
| if (type == CHIDataType:NCBWrData) { | |
| return Event:WriteData; | |
| } else { | |
| error("Invalid CHIDataType"); | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Data structures | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Cache block size | |
| int blockSize, default="RubySystem::getBlockSizeBytes()"; | |
| // TBE fields | |
| structure(TBE, desc="...") { | |
| int storSlot, desc="Slot in the storage tracker occupied by this entry"; | |
| Addr addr, desc="Line address for this TBE"; | |
| Addr accAddr, desc="Original access address. Set only for Write*Ptl"; | |
| int accSize, desc="Access size. Set only for Write*Ptl"; | |
| State state, desc="Current line state"; | |
| DataBlock dataBlk, desc="Transaction data"; | |
| WriteMask dataBlkValid, desc="valid bytes in dataBlk"; | |
| int rxtxBytes, desc="Bytes sent or received"; | |
| MachineID requestor, desc="Requestor that originated this request"; | |
| MachineID destination, desc="Where we are sending data"; | |
| bool useDataSepResp, desc="Replies with DataSepResp instead of CompData"; | |
| } | |
| structure(TBETable, external ="yes") { | |
| TBE lookup(Addr); | |
| void allocate(Addr); | |
| void deallocate(Addr); | |
| bool isPresent(Addr); | |
| bool areNSlotsAvailable(int n, Tick curTime); | |
| } | |
| structure(TBEStorage, external ="yes") { | |
| int size(); | |
| int capacity(); | |
| int reserved(); | |
| int slotsAvailable(); | |
| bool areNSlotsAvailable(int n); | |
| void incrementReserved(); | |
| void decrementReserved(); | |
| int addEntryToNewSlot(); | |
| void removeEntryFromSlot(int slot); | |
| } | |
| TBETable TBEs, template="<Memory_TBE>", constructor="m_number_of_TBEs"; | |
| TBEStorage storTBEs, constructor="this, m_number_of_TBEs"; | |
| // Tracks all pending MemoryAcks (debug purposes only) | |
| int pendingWrites, default="0"; | |
| structure(TriggerMsg, desc="...", interface="Message") { | |
| Addr addr; | |
| Event event; | |
| MachineID retryDest; | |
| bool functionalRead(Packet *pkt) { return false; } | |
| bool functionalRead(Packet *pkt, WriteMask &mask) { return false; } | |
| bool functionalWrite(Packet *pkt) { return false; } | |
| } | |
| // Tracks a pending credit request from a retry | |
| structure(RetryQueueEntry) { | |
| Addr addr, desc="Line address"; | |
| MachineID retryDest, desc="Retry destination"; | |
| } | |
| structure(TriggerQueue, external ="yes") { | |
| void pop(); | |
| bool empty(); | |
| void emplace(Addr,MachineID); | |
| RetryQueueEntry next(); | |
| } | |
| TriggerQueue retryQueue, template="<Memory_RetryQueueEntry>"; | |
| //////////////////////////////////////////////////////////////////////////// | |
| // External functions | |
| //////////////////////////////////////////////////////////////////////////// | |
| Tick clockEdge(); | |
| Tick curTick(); | |
| Tick cyclesToTicks(Cycles c); | |
| void set_tbe(TBE b); | |
| void unset_tbe(); | |
| void wakeUpAllBuffers(Addr a); | |
| bool respondsTo(Addr addr); | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Interface functions required by SLICC | |
| //////////////////////////////////////////////////////////////////////////// | |
| State getState(TBE tbe, Addr addr) { | |
| if (is_valid(tbe)) { | |
| assert(tbe.addr == addr); | |
| return tbe.state; | |
| } else { | |
| return State:READY; | |
| } | |
| } | |
| void setState(TBE tbe, Addr addr, State state) { | |
| if (is_valid(tbe)) { | |
| assert(tbe.addr == addr); | |
| tbe.state := state; | |
| } | |
| } | |
| AccessPermission getAccessPermission(Addr addr) { | |
| if (respondsTo(addr)) { | |
| TBE tbe := TBEs[addr]; | |
| if (is_valid(tbe)) { | |
| DPRINTF(RubySlicc, "%x %s,%s\n", addr, tbe.state, Memory_State_to_permission(tbe.state)); | |
| return Memory_State_to_permission(tbe.state); | |
| } else { | |
| DPRINTF(RubySlicc, "%x %s\n", addr, AccessPermission:Backing_Store); | |
| return AccessPermission:Backing_Store; | |
| } | |
| } else { | |
| DPRINTF(RubySlicc, "%x %s\n", addr, AccessPermission:NotPresent); | |
| return AccessPermission:NotPresent; | |
| } | |
| } | |
| void setAccessPermission(Addr addr, State state) { | |
| } | |
| void functionalRead(Addr addr, Packet *pkt, WriteMask &mask) { | |
| if (respondsTo(addr)) { | |
| DPRINTF(RubySlicc, "functionalRead %x\n", addr); | |
| TBE tbe := TBEs[addr]; | |
| if (mask.isEmpty()) { | |
| functionalMemoryRead(pkt); | |
| mask.fillMask(); | |
| DPRINTF(RubySlicc, "functionalRead mem %x %s\n", addr, mask); | |
| } | |
| // Update with any transient data | |
| //TODO additional handling of partial data ?? | |
| if (is_valid(tbe)) { | |
| WriteMask read_mask; | |
| read_mask.setMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize); | |
| read_mask.andMask(tbe.dataBlkValid); | |
| if (read_mask.isEmpty() == false) { | |
| testAndReadMask(addr, tbe.dataBlk, read_mask, pkt); | |
| DPRINTF(RubySlicc, "functionalRead tbe %x %s %s %s\n", addr, tbe.dataBlk, read_mask, mask); | |
| mask.orMask(read_mask); | |
| } | |
| } | |
| } | |
| } | |
| int functionalWrite(Addr addr, Packet *pkt) { | |
| if(respondsTo(addr)) { | |
| int num_functional_writes := 0; | |
| TBE tbe := TBEs[addr]; | |
| if (is_valid(tbe)) { | |
| num_functional_writes := num_functional_writes + | |
| testAndWrite(addr, tbe.dataBlk, pkt); | |
| DPRINTF(RubySlicc, "functionalWrite tbe %x %s\n", addr, tbe.dataBlk); | |
| } | |
| num_functional_writes := num_functional_writes + functionalMemoryWrite(pkt); | |
| DPRINTF(RubySlicc, "functionalWrite mem %x\n", addr); | |
| return num_functional_writes; | |
| } else { | |
| return 0; | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Helper functions | |
| //////////////////////////////////////////////////////////////////////////// | |
| void printResources() { | |
| DPRINTF(RubySlicc, "Resources(avail/max): TBEs=%d/%d\n", | |
| storTBEs.size(), storTBEs.capacity()); | |
| DPRINTF(RubySlicc, "Resources(in/out size): rdy=%d req=%d/%d rsp=%d/%d dat=%d/%d snp=%d/%d\n", | |
| reqRdy.getSize(curTick()), | |
| reqIn.getSize(curTick()), reqOut.getSize(curTick()), | |
| rspIn.getSize(curTick()), rspOut.getSize(curTick()), | |
| datIn.getSize(curTick()), datOut.getSize(curTick()), | |
| snpIn.getSize(curTick()), snpOut.getSize(curTick())); | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Input/output port definitions | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Outbound port definitions | |
| out_port(reqOutPort, CHIRequestMsg, reqOut); | |
| out_port(snpOutPort, CHIRequestMsg, snpOut); | |
| out_port(rspOutPort, CHIResponseMsg, rspOut); | |
| out_port(datOutPort, CHIDataMsg, datOut); | |
| out_port(triggerOutPort, TriggerMsg, triggerQueue); | |
| out_port(memQueue_out, MemoryMsg, requestToMemory); | |
| out_port(reqRdyOutPort, CHIRequestMsg, reqRdy); | |
| // Inbound port definitions | |
| // Response | |
| in_port(rspInPort, CHIResponseMsg, rspIn, rank=6) { | |
| if (rspInPort.isReady(clockEdge())) { | |
| printResources(); | |
| peek(rspInPort, CHIResponseMsg) { | |
| error("Unexpected message"); | |
| } | |
| } | |
| } | |
| // Data | |
| in_port(datInPort, CHIDataMsg, datIn, rank=5) { | |
| if (datInPort.isReady(clockEdge())) { | |
| printResources(); | |
| peek(datInPort, CHIDataMsg) { | |
| assert((in_msg.bitMask.count() <= data_channel_size) | |
| && (in_msg.bitMask.count() > 0)); | |
| trigger(dataToEvent(in_msg.type), in_msg.addr, TBEs[in_msg.addr]); | |
| } | |
| } | |
| } | |
| // Data/Ack from memory | |
| in_port(memQueue_in, MemoryMsg, responseFromMemory, rank=4) { | |
| if (memQueue_in.isReady(clockEdge())) { | |
| printResources(); | |
| peek(memQueue_in, MemoryMsg) { | |
| Addr addr := makeLineAddress(in_msg.addr); | |
| if (in_msg.Type == MemoryRequestType:MEMORY_READ) { | |
| trigger(Event:MemoryData, addr, TBEs[addr]); | |
| } else if (in_msg.Type == MemoryRequestType:MEMORY_WB) { | |
| trigger(Event:MemoryAck, addr, TBEs[addr]); | |
| } else { | |
| error("Invalid message"); | |
| } | |
| } | |
| } | |
| } | |
| // Trigger | |
| in_port(triggerInPort, TriggerMsg, triggerQueue, rank=3) { | |
| if (triggerInPort.isReady(clockEdge())) { | |
| printResources(); | |
| peek(triggerInPort, TriggerMsg) { | |
| trigger(in_msg.event, in_msg.addr, TBEs[in_msg.addr]); | |
| } | |
| } | |
| } | |
| // Snoops | |
| in_port(snpInPort, CHIRequestMsg, snpIn, rank=2) { | |
| if (snpInPort.isReady(clockEdge())) { | |
| printResources(); | |
| peek(snpInPort, CHIRequestMsg) { | |
| error("Unexpected message"); | |
| } | |
| } | |
| } | |
| // Requests | |
| in_port(reqRdyInPort, CHIRequestMsg, reqRdy, rank=1) { | |
| if (reqRdyInPort.isReady(clockEdge())) { | |
| printResources(); | |
| peek(reqRdyInPort, CHIRequestMsg) { | |
| trigger(reqToEvent(in_msg.type), in_msg.addr, TBEs[in_msg.addr]); | |
| } | |
| } | |
| } | |
| in_port(reqInPort, CHIRequestMsg, reqIn, rank=0) { | |
| if (reqInPort.isReady(clockEdge())) { | |
| printResources(); | |
| peek(reqInPort, CHIRequestMsg) { | |
| if (in_msg.allowRetry) { | |
| trigger(Event:CheckAllocTBE, in_msg.addr, TBEs[in_msg.addr]); | |
| } else { | |
| // Only expected requests that do not allow retry are the ones that | |
| // are being retried after receiving credit | |
| trigger(Event:CheckAllocTBE_WithCredit, | |
| in_msg.addr, TBEs[in_msg.addr]); | |
| } | |
| } | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Actions | |
| //////////////////////////////////////////////////////////////////////////// | |
| action(checkAllocateTBE, desc="") { | |
| // Move to reqRdy if resources available, otherwise send retry | |
| if (storTBEs.areNSlotsAvailable(1)) { | |
| // reserve a slot for this request | |
| storTBEs.incrementReserved(); | |
| peek(reqInPort, CHIRequestMsg) { | |
| enqueue(reqRdyOutPort, CHIRequestMsg, 0) { | |
| out_msg := in_msg; | |
| } | |
| } | |
| } else { | |
| peek(reqInPort, CHIRequestMsg) { | |
| assert(in_msg.allowRetry); | |
| enqueue(triggerOutPort, TriggerMsg, 0) { | |
| out_msg.addr := in_msg.addr; | |
| out_msg.event := Event:Trigger_SendRetry; | |
| out_msg.retryDest := in_msg.requestor; | |
| retryQueue.emplace(in_msg.addr,in_msg.requestor); | |
| } | |
| } | |
| } | |
| reqInPort.dequeue(clockEdge()); | |
| } | |
| action(checkAllocateTBE_withCredit, desc="") { | |
| // We must have reserved resources for this request | |
| peek(reqInPort, CHIRequestMsg) { | |
| assert(in_msg.allowRetry == false); | |
| enqueue(reqRdyOutPort, CHIRequestMsg, 0) { | |
| out_msg := in_msg; | |
| } | |
| } | |
| reqInPort.dequeue(clockEdge()); | |
| } | |
| action(allocateTBE, "atbe", desc="Allocate TBEs for a miss") { | |
| // We must have reserved resources for this allocation | |
| storTBEs.decrementReserved(); | |
| assert(storTBEs.areNSlotsAvailable(1)); | |
| TBEs.allocate(address); | |
| set_tbe(TBEs[address]); | |
| tbe.storSlot := storTBEs.addEntryToNewSlot(); | |
| tbe.addr := address; | |
| tbe.rxtxBytes := 0; | |
| tbe.useDataSepResp := false; | |
| } | |
| action(initializeFromReqTBE, "itbe", desc="Initialize TBE fields") { | |
| peek(reqRdyInPort, CHIRequestMsg) { | |
| tbe.requestor := in_msg.requestor; | |
| if (in_msg.dataToFwdRequestor) { | |
| tbe.destination := in_msg.fwdRequestor; | |
| } else { | |
| tbe.destination := in_msg.requestor; | |
| } | |
| tbe.accAddr := in_msg.accAddr; | |
| tbe.accSize := in_msg.accSize; | |
| } | |
| } | |
| action(decWritePending, "dwp", desc="Decrement pending writes") { | |
| assert(pendingWrites >= 1); | |
| pendingWrites := pendingWrites - 1; | |
| } | |
| action(deallocateTBE, "dtbe", desc="Deallocate TBEs") { | |
| assert(is_valid(tbe)); | |
| storTBEs.removeEntryFromSlot(tbe.storSlot); | |
| TBEs.deallocate(address); | |
| unset_tbe(); | |
| // send credit if requestor waiting for it | |
| if (retryQueue.empty() == false) { | |
| assert(storTBEs.areNSlotsAvailable(1)); | |
| storTBEs.incrementReserved(); | |
| RetryQueueEntry e := retryQueue.next(); | |
| retryQueue.pop(); | |
| enqueue(triggerOutPort, TriggerMsg, 0) { | |
| out_msg.addr := e.addr; | |
| out_msg.retryDest := e.retryDest; | |
| out_msg.event := Event:Trigger_SendPCrdGrant; | |
| } | |
| } | |
| } | |
| action(sendReadReceipt, "sRR", desc="Send receipt to requestor") { | |
| assert(is_valid(tbe)); | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := address; | |
| out_msg.type := CHIResponseType:ReadReceipt; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(tbe.requestor); | |
| } | |
| // also send different type of data when ready | |
| tbe.useDataSepResp := true; | |
| } | |
| action(sendCompDBIDResp, "sCbid", desc="Send ack to requestor") { | |
| 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(sendMemoryRead, "smr", desc="Send request to memory") { | |
| assert(is_valid(tbe)); | |
| enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { | |
| out_msg.addr := address; | |
| out_msg.Type := MemoryRequestType:MEMORY_READ; | |
| out_msg.Sender := tbe.requestor; | |
| out_msg.MessageSize := MessageSizeType:Request_Control; | |
| out_msg.Len := 0; | |
| } | |
| } | |
| action(sendMemoryWrite, "smw", desc="Send request to memory") { | |
| assert(is_valid(tbe)); | |
| enqueue(memQueue_out, MemoryMsg, to_memory_controller_latency) { | |
| out_msg.addr := tbe.accAddr; | |
| out_msg.Type := MemoryRequestType:MEMORY_WB; | |
| out_msg.Sender := tbe.requestor; | |
| out_msg.MessageSize := MessageSizeType:Writeback_Data; | |
| out_msg.DataBlk := tbe.dataBlk; | |
| out_msg.Len := tbe.accSize; | |
| } | |
| tbe.dataBlkValid.clear(); | |
| pendingWrites := pendingWrites + 1; | |
| } | |
| action(prepareSend, "ps", desc="Copies received memory data to TBE") { | |
| assert(is_valid(tbe)); | |
| peek(memQueue_in, MemoryMsg) { | |
| tbe.dataBlk := in_msg.DataBlk; | |
| } | |
| tbe.rxtxBytes := 0; | |
| tbe.dataBlkValid.setMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize); | |
| } | |
| action(copyWriteDataToTBE, "cpWDat", desc="Copies received net data to TBE") { | |
| peek(datInPort, CHIDataMsg) { | |
| assert(is_valid(tbe)); | |
| tbe.dataBlk.copyPartial(in_msg.dataBlk, in_msg.bitMask); | |
| tbe.dataBlkValid.orMask(in_msg.bitMask); | |
| tbe.rxtxBytes := tbe.rxtxBytes + in_msg.bitMask.count(); | |
| } | |
| } | |
| action(sendDataAndCheck, "sd", desc="Send received data to requestor") { | |
| assert(is_valid(tbe)); | |
| assert(tbe.rxtxBytes < blockSize); | |
| enqueue(datOutPort, CHIDataMsg, data_latency) { | |
| out_msg.addr := tbe.addr; | |
| if (tbe.useDataSepResp) { | |
| out_msg.type := CHIDataType:DataSepResp_UC; | |
| } else { | |
| out_msg.type := CHIDataType:CompData_UC; | |
| } | |
| out_msg.dataBlk := tbe.dataBlk; | |
| // Called in order for the whole block so use rxtxBytes as offset | |
| out_msg.bitMask.setMask(tbe.rxtxBytes, data_channel_size); | |
| out_msg.Destination.add(tbe.destination); | |
| } | |
| //DPRINTF(RubySlicc, "rxtxBytes=%d\n", tbe.rxtxBytes); | |
| tbe.rxtxBytes := tbe.rxtxBytes + data_channel_size; | |
| // end or send next chunk next cycle | |
| Event next := Event:Trigger_SendDone; | |
| Cycles delay := intToCycles(0); | |
| if (tbe.rxtxBytes < blockSize) { | |
| next := Event:Trigger_Send; | |
| delay := intToCycles(1); | |
| } | |
| enqueue(triggerOutPort, TriggerMsg, delay) { | |
| out_msg.addr := address; | |
| out_msg.event := next; | |
| } | |
| } | |
| action(checkForReceiveCompletion, "cWc", desc="Check if all data is received") { | |
| assert(is_valid(tbe)); | |
| DPRINTF(RubySlicc, "rxtxBytes=%d\n", tbe.rxtxBytes); | |
| assert((tbe.rxtxBytes <= tbe.accSize) && (tbe.rxtxBytes > 0)); | |
| if (tbe.rxtxBytes == tbe.accSize) { | |
| enqueue(triggerOutPort, TriggerMsg, 0) { | |
| out_msg.addr := address; | |
| out_msg.event := Event:Trigger_ReceiveDone; | |
| } | |
| tbe.rxtxBytes := 0; | |
| assert(tbe.dataBlkValid.getMask(addressOffset(tbe.accAddr, tbe.addr), tbe.accSize)); | |
| } | |
| } | |
| action(popReqInQueue, "preq", desc="Pop request queue.") { | |
| reqRdyInPort.dequeue(clockEdge()); | |
| } | |
| action(popDataInQueue, "pdata", desc="Pop data queue.") { | |
| datInPort.dequeue(clockEdge()); | |
| } | |
| action(popTriggerQueue, "ptrigger", desc="Pop trigger queue.") { | |
| triggerInPort.dequeue(clockEdge()); | |
| } | |
| action(popMemoryQueue, "pmem", desc="Pop memory queue.") { | |
| memQueue_in.dequeue(clockEdge()); | |
| } | |
| // Stall/wake-up only used for requests that arrive when we are on the | |
| // WAITING_NET_DATA state. For all other case the line should be either | |
| // ready or we can overlap | |
| action(stallRequestQueue, "str", desc="Stall and wait on the address") { | |
| peek(reqRdyInPort, CHIRequestMsg){ | |
| stall_and_wait(reqRdyInPort, address); | |
| } | |
| } | |
| action(wakeUpStalled, "wa", desc="Wake up any requests waiting for this address") { | |
| wakeUpAllBuffers(address); | |
| } | |
| action(sendRetryAck, desc="") { | |
| peek(triggerInPort, TriggerMsg) { | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := in_msg.addr; | |
| out_msg.type := CHIResponseType:RetryAck; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(in_msg.retryDest); | |
| } | |
| } | |
| } | |
| action(sendPCrdGrant, desc="") { | |
| peek(triggerInPort, TriggerMsg) { | |
| enqueue(rspOutPort, CHIResponseMsg, response_latency) { | |
| out_msg.addr := in_msg.addr; | |
| out_msg.type := CHIResponseType:PCrdGrant; | |
| out_msg.responder := machineID; | |
| out_msg.Destination.add(in_msg.retryDest); | |
| } | |
| } | |
| } | |
| //////////////////////////////////////////////////////////////////////////// | |
| // Transitions | |
| //////////////////////////////////////////////////////////////////////////// | |
| transition(READY, ReadNoSnp, READING_MEM) { | |
| allocateTBE; | |
| initializeFromReqTBE; | |
| sendMemoryRead; | |
| popReqInQueue; | |
| } | |
| transition(READY, ReadNoSnpSep, READING_MEM) { | |
| allocateTBE; | |
| initializeFromReqTBE; | |
| sendMemoryRead; | |
| sendReadReceipt; | |
| popReqInQueue; | |
| } | |
| transition(READING_MEM, MemoryData, SENDING_NET_DATA) { | |
| prepareSend; | |
| sendDataAndCheck; | |
| popMemoryQueue; | |
| } | |
| transition(SENDING_NET_DATA, Trigger_Send) { | |
| sendDataAndCheck; | |
| popTriggerQueue; | |
| } | |
| transition(READY, WriteNoSnpPtl, WAITING_NET_DATA) { | |
| allocateTBE; | |
| initializeFromReqTBE; | |
| sendCompDBIDResp; | |
| popReqInQueue; | |
| } | |
| transition(READY, WriteNoSnp, WAITING_NET_DATA) { | |
| allocateTBE; | |
| initializeFromReqTBE; | |
| sendCompDBIDResp; | |
| popReqInQueue; | |
| } | |
| transition(WAITING_NET_DATA, WriteData) { | |
| copyWriteDataToTBE; | |
| checkForReceiveCompletion; | |
| popDataInQueue; | |
| } | |
| transition(WAITING_NET_DATA, Trigger_ReceiveDone, READY) { | |
| sendMemoryWrite; | |
| deallocateTBE; | |
| wakeUpStalled; | |
| popTriggerQueue; | |
| } | |
| transition(SENDING_NET_DATA, Trigger_SendDone, READY) { | |
| deallocateTBE; | |
| wakeUpStalled; | |
| popTriggerQueue; | |
| } | |
| // Just sanity check against counter of pending acks | |
| transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY}, | |
| MemoryAck) { | |
| decWritePending; | |
| popMemoryQueue; | |
| } | |
| // Notice we only use this here and call wakeUp when leaving this state | |
| transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA}, | |
| {ReadNoSnp, ReadNoSnpSep, WriteNoSnpPtl}) { | |
| stallRequestQueue; | |
| } | |
| transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY}, | |
| Trigger_SendRetry) { | |
| sendRetryAck; | |
| popTriggerQueue; | |
| } | |
| transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY}, | |
| Trigger_SendPCrdGrant) { | |
| sendPCrdGrant; | |
| popTriggerQueue; | |
| } | |
| transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY}, | |
| CheckAllocTBE) { | |
| checkAllocateTBE; | |
| } | |
| transition({READING_MEM,WAITING_NET_DATA,SENDING_NET_DATA,READY}, | |
| CheckAllocTBE_WithCredit) { | |
| checkAllocateTBE_withCredit; | |
| } | |
| } |