| /* |
| * 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. |
| */ |
| |
| |
| 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=""; |
| Trigger_SendMemoryRead, 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(triggerSendMemoryRead, "tsmr", desc="Trigger sendMemoryRead") { |
| assert(is_valid(tbe)); |
| enqueue(triggerOutPort, TriggerMsg, 0) { |
| out_msg.addr := address; |
| out_msg.event := Event:Trigger_SendMemoryRead; |
| } |
| } |
| |
| 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; |
| triggerSendMemoryRead; |
| popReqInQueue; |
| } |
| |
| transition(READY, ReadNoSnpSep, READING_MEM) { |
| allocateTBE; |
| initializeFromReqTBE; |
| triggerSendMemoryRead; |
| sendReadReceipt; |
| popReqInQueue; |
| } |
| |
| transition(READING_MEM, Trigger_SendMemoryRead) { |
| sendMemoryRead; |
| popTriggerQueue; |
| } |
| |
| 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, WriteNoSnp}) { |
| 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; |
| } |
| |
| } |