| /* |
| * 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. |
| */ |
| |
| // ---- Outbound port definitions ---- |
| // Network interfaces |
| out_port(reqOutPort, CHIRequestMsg, reqOut); |
| out_port(snpOutPort, CHIRequestMsg, snpOut); |
| out_port(rspOutPort, CHIResponseMsg, rspOut); |
| out_port(datOutPort, CHIDataMsg, datOut); |
| // Internal output ports |
| out_port(triggerOutPort, TriggerMsg, triggerQueue); |
| out_port(retryTriggerOutPort, RetryTriggerMsg, retryTriggerQueue); |
| out_port(schedRspTriggerOutPort, CHIResponseMsg, schedRspTriggerQueue); |
| out_port(reqRdyOutPort, CHIRequestMsg, reqRdy); |
| out_port(snpRdyOutPort, CHIRequestMsg, snpRdy); |
| |
| |
| // Include helper functions here. Some of them require the outports to be |
| // already defined |
| // Notice 'processNextState' and 'wakeupPending*' functions are defined after |
| // the required input ports. Currently the SLICC compiler does not support |
| // separate declaration and definition of functions in the .sm files. |
| include "CHI-dvm-misc-node-funcs.sm"; |
| |
| |
| // Inbound port definitions and internal triggers queues |
| // Notice we never stall input ports connected to the network |
| // Incoming data and responses are always consumed. |
| // Incoming requests/snoop are moved to the respective internal rdy queue |
| // if a TBE can be allocated, or retried otherwise. |
| |
| // Response |
| in_port(rspInPort, CHIResponseMsg, rspIn, rank=11, |
| rsc_stall_handler=rspInPort_rsc_stall_handler) { |
| if (rspInPort.isReady(clockEdge())) { |
| printResources(); |
| peek(rspInPort, CHIResponseMsg) { |
| assert(in_msg.usesTxnId); |
| TBE tbe := getCurrentActiveTBE(in_msg.txnId); |
| trigger(respToEvent(in_msg.type), in_msg.txnId, tbe); |
| } |
| } |
| } |
| bool rspInPort_rsc_stall_handler() { |
| error("rspInPort must never stall\n"); |
| return false; |
| } |
| |
| |
| // Data |
| in_port(datInPort, CHIDataMsg, datIn, rank=10, |
| rsc_stall_handler=datInPort_rsc_stall_handler) { |
| if (datInPort.isReady(clockEdge())) { |
| printResources(); |
| peek(datInPort, CHIDataMsg) { |
| assert((in_msg.bitMask.count() <= data_channel_size) && (in_msg.bitMask.count() > 0)); |
| assert(in_msg.usesTxnId); |
| trigger(dataToEvent(in_msg.type), in_msg.txnId, getCurrentActiveTBE(in_msg.txnId)); |
| } |
| } |
| } |
| bool datInPort_rsc_stall_handler() { |
| error("datInPort must never stall\n"); |
| return false; |
| } |
| |
| // Incoming snoops - should never be used |
| in_port(snpInPort, CHIRequestMsg, snpIn, rank=8) { |
| if (snpInPort.isReady(clockEdge())) { |
| printResources(); |
| peek(snpInPort, CHIRequestMsg) { |
| error("MN should not receive snoops"); |
| } |
| } |
| } |
| bool snpInPort_rsc_stall_handler() { |
| error("snpInPort must never stall\n"); |
| return false; |
| } |
| |
| // Incoming new requests |
| in_port(reqInPort, CHIRequestMsg, reqIn, rank=2, |
| rsc_stall_handler=reqInPort_rsc_stall_handler) { |
| if (reqInPort.isReady(clockEdge())) { |
| printResources(); |
| peek(reqInPort, CHIRequestMsg) { |
| assert(in_msg.usesTxnId); |
| // Make sure we aren't already processing this |
| assert(!is_valid(getCurrentActiveTBE(in_msg.txnId))); |
| if (in_msg.allowRetry) { |
| trigger(Event:AllocRequest, in_msg.txnId, nullTBE()); |
| } else { |
| trigger(Event:AllocRequestWithCredit, in_msg.txnId, nullTBE()); |
| } |
| } |
| } |
| } |
| bool reqInPort_rsc_stall_handler() { |
| error("reqInPort must never stall\n"); |
| return false; |
| } |
| |
| |
| // Incoming new sequencer requests |
| in_port(seqInPort, RubyRequest, mandatoryQueue, rank=1) { |
| if (seqInPort.isReady(clockEdge())) { |
| printResources(); |
| peek(seqInPort, RubyRequest) { |
| error("MN should not have sequencer"); |
| } |
| } |
| } |
| |
| |
| // Action triggers |
| in_port(triggerInPort, TriggerMsg, triggerQueue, rank=5, |
| rsc_stall_handler=triggerInPort_rsc_stall_handler) { |
| if (triggerInPort.isReady(clockEdge())) { |
| printResources(); |
| peek(triggerInPort, TriggerMsg) { |
| TBE tbe := getCurrentActiveTBE(in_msg.txnId); |
| assert(is_valid(tbe)); |
| assert(!in_msg.from_hazard); |
| trigger(tbe.pendAction, in_msg.txnId, tbe); |
| } |
| } |
| } |
| bool triggerInPort_rsc_stall_handler() { |
| DPRINTF(RubySlicc, "Trigger queue resource stall\n"); |
| triggerInPort.recycle(clockEdge(), cyclesToTicks(stall_recycle_lat)); |
| return true; |
| } |
| void wakeupPendingTgrs(TBE tbe) { |
| if (tbe.wakeup_pending_tgr) { |
| Addr txnId := tbe.txnId; |
| wakeup_port(triggerInPort, txnId); |
| tbe.wakeup_pending_tgr := false; |
| } |
| } |
| |
| // Requests with an allocated TBE |
| in_port(reqRdyPort, CHIRequestMsg, reqRdy, rank=3, |
| rsc_stall_handler=reqRdyPort_rsc_stall_handler) { |
| if (reqRdyPort.isReady(clockEdge())) { |
| printResources(); |
| peek(reqRdyPort, CHIRequestMsg) { |
| assert(in_msg.usesTxnId); |
| TBE tbe := getCurrentActiveTBE(in_msg.txnId); |
| assert(!in_msg.is_local_pf); |
| // Normal request path |
| trigger(reqToEvent(in_msg.type), in_msg.txnId, tbe); |
| } |
| } |
| } |
| bool reqRdyPort_rsc_stall_handler() { |
| DPRINTF(RubySlicc, "ReqRdy queue resource stall\n"); |
| reqRdyPort.recycle(clockEdge(), cyclesToTicks(stall_recycle_lat)); |
| return true; |
| } |
| void wakeupPendingReqs(TBE tbe) { |
| if (tbe.wakeup_pending_req) { |
| Addr txnId := tbe.txnId; |
| wakeup_port(reqRdyPort, txnId); |
| tbe.wakeup_pending_req := false; |
| } |
| } |
| |
| |
| // Retry action triggers |
| // These are handled separately from other triggers since these events are |
| // not tied to a TBE |
| in_port(retryTriggerInPort, RetryTriggerMsg, retryTriggerQueue, rank=7) { |
| if (retryTriggerInPort.isReady(clockEdge())) { |
| printResources(); |
| peek(retryTriggerInPort, RetryTriggerMsg) { |
| Event ev := in_msg.event; |
| TBE tbe := getCurrentActiveTBE(in_msg.txnId); |
| assert((ev == Event:SendRetryAck) || (ev == Event:SendPCrdGrant)); |
| trigger(ev, in_msg.txnId, tbe); |
| } |
| } |
| } |
| |
| // Trigger queue for scheduled responses so transactions don't need to |
| // block on a response when the rspOutPort is busy |
| in_port(schedRspTriggerInPort, CHIResponseMsg, schedRspTriggerQueue, rank=6) { |
| if (schedRspTriggerInPort.isReady(clockEdge())) { |
| printResources(); |
| peek(schedRspTriggerInPort, CHIResponseMsg) { |
| error("Misc Node shouldn't have schedResp"); |
| } |
| } |
| } |
| |
| // Enqueues next event depending on the pending actions and the event queue |
| void processNextState(TBE tbe) { |
| assert(is_valid(tbe)); |
| DPRINTF(RubyProtocol, "GoToNextState state=%d expected_req_resp=%d expected_snp_resp=%d sched_rsp=%d(block=%d) pendAction: %d\n", |
| tbe.state, |
| tbe.expected_req_resp.expected(), |
| tbe.expected_snp_resp.expected(), |
| tbe.sched_responses, tbe.block_on_sched_responses, |
| tbe.pendAction); |
| |
| // if no pending trigger and not expecting to receive anything, enqueue |
| // next |
| bool has_nb_trigger := (tbe.actions.empty() == false) && |
| tbe.actions.frontNB(); |
| int expected_msgs := tbe.expected_req_resp.expected() + |
| tbe.expected_snp_resp.expected(); |
| if (tbe.block_on_sched_responses) { |
| expected_msgs := expected_msgs + tbe.sched_responses; |
| tbe.block_on_sched_responses := tbe.sched_responses > 0; |
| } |
| |
| // If we are waiting on other transactions to finish, we shouldn't enqueue Final |
| bool would_enqueue_final := tbe.actions.empty(); |
| bool allowed_to_enqueue_final := !tbe.waiting_on_other_txns; |
| // if (would_enqueue_final && !allowed) then DON'T enqueue anything |
| // => if (!would_enqueue_final || allowed_to_enqueue_final) then DO |
| bool allowed_to_enqueue_action := !would_enqueue_final || allowed_to_enqueue_final; |
| |
| if ((tbe.pendAction == Event:null) && |
| ((expected_msgs == 0) || has_nb_trigger) && |
| allowed_to_enqueue_action) { |
| Cycles trigger_latency := intToCycles(0); |
| if (tbe.delayNextAction > curTick()) { |
| trigger_latency := ticksToCycles(tbe.delayNextAction) - |
| ticksToCycles(curTick()); |
| tbe.delayNextAction := intToTick(0); |
| } |
| |
| tbe.pendAction := Event:null; |
| if (tbe.actions.empty()) { |
| // time to go to the final state |
| tbe.pendAction := Event:Final; |
| } else { |
| tbe.pendAction := tbe.actions.front(); |
| tbe.actions.pop(); |
| } |
| assert(tbe.pendAction != Event:null); |
| enqueue(triggerOutPort, TriggerMsg, trigger_latency) { |
| out_msg.txnId := tbe.txnId; |
| out_msg.from_hazard := false; |
| } |
| } |
| |
| printTBEState(tbe); |
| } |
| |
| // Runs at the end of every cycle that takes input, checks `needsToCheckPendingOps`. |
| // if true, will call updatePendingOps() to check if a new snoop-sender should start. |
| // We could return bools if we want to be sure we run on the next cycle, |
| // but we have no reason to do that |
| void updatePendingOps(), run_on_input_cycle_end="yes" { |
| if (needsToCheckPendingOps) { |
| needsToCheckPendingOps := false; |
| DPRINTF(RubyProtocol, "Misc Node updating pending ops\n"); |
| TBE newDistributor := dvmTBEs.chooseNewDistributor(); |
| DPRINTF(RubyProtocol, "Misc Node selected %p\n", newDistributor); |
| if (is_valid(newDistributor)) { |
| // can return the current distributor, check for that |
| if (!hasCurrentDistributor || newDistributor.txnId != currentDistributor) { |
| currentDistributor := newDistributor.txnId; |
| hasCurrentDistributor := true; |
| |
| // make the new distributor start distributing |
| // by simply telling it to send the next message |
| newDistributor.actions.pushNB(Event:DvmSendNextMessage_P1); |
| processNextState(newDistributor); |
| |
| // TODO could move into Profile_OutgoingStart_DVM |
| // Use a useful event name for profiling |
| Event usefulEvent := Event:DvmSync_Initiate; |
| if (newDistributor.isNonSync) { |
| usefulEvent := Event:DvmTlbi_Initiate; |
| } |
| outgoingTransactionStart(newDistributor.txnId, usefulEvent, false); |
| } |
| } |
| } |
| } |