| /* |
| * Copyright (c) 2008 Princeton University |
| * Copyright (c) 2016 Georgia Institute of Technology |
| * All rights reserved. |
| * |
| * 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. |
| * |
| * Authors: Niket Agarwal |
| * Tushar Krishna |
| */ |
| |
| |
| #include "mem/ruby/network/garnet2.0/SwitchAllocator.hh" |
| |
| #include "debug/RubyNetwork.hh" |
| #include "mem/ruby/network/garnet2.0/GarnetNetwork.hh" |
| #include "mem/ruby/network/garnet2.0/InputUnit.hh" |
| #include "mem/ruby/network/garnet2.0/OutputUnit.hh" |
| #include "mem/ruby/network/garnet2.0/Router.hh" |
| |
| SwitchAllocator::SwitchAllocator(Router *router) |
| : Consumer(router) |
| { |
| m_router = router; |
| m_num_vcs = m_router->get_num_vcs(); |
| m_vc_per_vnet = m_router->get_vc_per_vnet(); |
| |
| m_input_arbiter_activity = 0; |
| m_output_arbiter_activity = 0; |
| } |
| |
| void |
| SwitchAllocator::init() |
| { |
| m_input_unit = m_router->get_inputUnit_ref(); |
| m_output_unit = m_router->get_outputUnit_ref(); |
| |
| m_num_inports = m_router->get_num_inports(); |
| m_num_outports = m_router->get_num_outports(); |
| m_round_robin_inport.resize(m_num_outports); |
| m_round_robin_invc.resize(m_num_inports); |
| m_port_requests.resize(m_num_outports); |
| m_vc_winners.resize(m_num_outports); |
| |
| for (int i = 0; i < m_num_inports; i++) { |
| m_round_robin_invc[i] = 0; |
| } |
| |
| for (int i = 0; i < m_num_outports; i++) { |
| m_port_requests[i].resize(m_num_inports); |
| m_vc_winners[i].resize(m_num_inports); |
| |
| m_round_robin_inport[i] = 0; |
| |
| for (int j = 0; j < m_num_inports; j++) { |
| m_port_requests[i][j] = false; // [outport][inport] |
| } |
| } |
| } |
| |
| /* |
| * The wakeup function of the SwitchAllocator performs a 2-stage |
| * seperable switch allocation. At the end of the 2nd stage, a free |
| * output VC is assigned to the winning flits of each output port. |
| * There is no separate VCAllocator stage like the one in garnet1.0. |
| * At the end of this function, the router is rescheduled to wakeup |
| * next cycle for peforming SA for any flits ready next cycle. |
| */ |
| |
| void |
| SwitchAllocator::wakeup() |
| { |
| arbitrate_inports(); // First stage of allocation |
| arbitrate_outports(); // Second stage of allocation |
| |
| clear_request_vector(); |
| check_for_wakeup(); |
| } |
| |
| /* |
| * SA-I (or SA-i) loops through all input VCs at every input port, |
| * and selects one in a round robin manner. |
| * - For HEAD/HEAD_TAIL flits only selects an input VC whose output port |
| * has at least one free output VC. |
| * - For BODY/TAIL flits, only selects an input VC that has credits |
| * in its output VC. |
| * Places a request for the output port from this input VC. |
| */ |
| |
| void |
| SwitchAllocator::arbitrate_inports() |
| { |
| // Select a VC from each input in a round robin manner |
| // Independent arbiter at each input port |
| for (int inport = 0; inport < m_num_inports; inport++) { |
| int invc = m_round_robin_invc[inport]; |
| |
| for (int invc_iter = 0; invc_iter < m_num_vcs; invc_iter++) { |
| |
| if (m_input_unit[inport]->need_stage(invc, SA_, |
| m_router->curCycle())) { |
| |
| // This flit is in SA stage |
| |
| int outport = m_input_unit[inport]->get_outport(invc); |
| int outvc = m_input_unit[inport]->get_outvc(invc); |
| |
| // check if the flit in this InputVC is allowed to be sent |
| // send_allowed conditions described in that function. |
| bool make_request = |
| send_allowed(inport, invc, outport, outvc); |
| |
| if (make_request) { |
| m_input_arbiter_activity++; |
| m_port_requests[outport][inport] = true; |
| m_vc_winners[outport][inport]= invc; |
| |
| // Update Round Robin pointer to the next VC |
| m_round_robin_invc[inport] = invc + 1; |
| if (m_round_robin_invc[inport] >= m_num_vcs) |
| m_round_robin_invc[inport] = 0; |
| |
| break; // got one vc winner for this port |
| } |
| } |
| |
| invc++; |
| if (invc >= m_num_vcs) |
| invc = 0; |
| } |
| } |
| } |
| |
| /* |
| * SA-II (or SA-o) loops through all output ports, |
| * and selects one input VC (that placed a request during SA-I) |
| * as the winner for this output port in a round robin manner. |
| * - For HEAD/HEAD_TAIL flits, performs simplified outvc allocation. |
| * (i.e., select a free VC from the output port). |
| * - For BODY/TAIL flits, decrement a credit in the output vc. |
| * The winning flit is read out from the input VC and sent to the |
| * CrossbarSwitch. |
| * An increment_credit signal is sent from the InputUnit |
| * to the upstream router. For HEAD_TAIL/TAIL flits, is_free_signal in the |
| * credit is set to true. |
| */ |
| |
| void |
| SwitchAllocator::arbitrate_outports() |
| { |
| // Now there are a set of input vc requests for output vcs. |
| // Again do round robin arbitration on these requests |
| // Independent arbiter at each output port |
| for (int outport = 0; outport < m_num_outports; outport++) { |
| int inport = m_round_robin_inport[outport]; |
| |
| for (int inport_iter = 0; inport_iter < m_num_inports; |
| inport_iter++) { |
| |
| // inport has a request this cycle for outport |
| if (m_port_requests[outport][inport]) { |
| |
| // grant this outport to this inport |
| int invc = m_vc_winners[outport][inport]; |
| |
| int outvc = m_input_unit[inport]->get_outvc(invc); |
| if (outvc == -1) { |
| // VC Allocation - select any free VC from outport |
| outvc = vc_allocate(outport, inport, invc); |
| } |
| |
| // remove flit from Input VC |
| flit *t_flit = m_input_unit[inport]->getTopFlit(invc); |
| |
| DPRINTF(RubyNetwork, "SwitchAllocator at Router %d " |
| "granted outvc %d at outport %d " |
| "to invc %d at inport %d to flit %s at " |
| "time: %lld\n", |
| m_router->get_id(), outvc, |
| m_router->getPortDirectionName( |
| m_output_unit[outport]->get_direction()), |
| invc, |
| m_router->getPortDirectionName( |
| m_input_unit[inport]->get_direction()), |
| *t_flit, |
| m_router->curCycle()); |
| |
| |
| // Update outport field in the flit since this is |
| // used by CrossbarSwitch code to send it out of |
| // correct outport. |
| // Note: post route compute in InputUnit, |
| // outport is updated in VC, but not in flit |
| t_flit->set_outport(outport); |
| |
| // set outvc (i.e., invc for next hop) in flit |
| // (This was updated in VC by vc_allocate, but not in flit) |
| t_flit->set_vc(outvc); |
| |
| // decrement credit in outvc |
| m_output_unit[outport]->decrement_credit(outvc); |
| |
| // flit ready for Switch Traversal |
| t_flit->advance_stage(ST_, m_router->curCycle()); |
| m_router->grant_switch(inport, t_flit); |
| m_output_arbiter_activity++; |
| |
| if ((t_flit->get_type() == TAIL_) || |
| t_flit->get_type() == HEAD_TAIL_) { |
| |
| // This Input VC should now be empty |
| assert(!(m_input_unit[inport]->isReady(invc, |
| m_router->curCycle()))); |
| |
| // Free this VC |
| m_input_unit[inport]->set_vc_idle(invc, |
| m_router->curCycle()); |
| |
| // Send a credit back |
| // along with the information that this VC is now idle |
| m_input_unit[inport]->increment_credit(invc, true, |
| m_router->curCycle()); |
| } else { |
| // Send a credit back |
| // but do not indicate that the VC is idle |
| m_input_unit[inport]->increment_credit(invc, false, |
| m_router->curCycle()); |
| } |
| |
| // remove this request |
| m_port_requests[outport][inport] = false; |
| |
| // Update Round Robin pointer |
| m_round_robin_inport[outport] = inport + 1; |
| if (m_round_robin_inport[outport] >= m_num_inports) |
| m_round_robin_inport[outport] = 0; |
| |
| break; // got a input winner for this outport |
| } |
| |
| inport++; |
| if (inport >= m_num_inports) |
| inport = 0; |
| } |
| } |
| } |
| |
| /* |
| * A flit can be sent only if |
| * (1) there is at least one free output VC at the |
| * output port (for HEAD/HEAD_TAIL), |
| * or |
| * (2) if there is at least one credit (i.e., buffer slot) |
| * within the VC for BODY/TAIL flits of multi-flit packets. |
| * and |
| * (3) pt-to-pt ordering is not violated in ordered vnets, i.e., |
| * there should be no other flit in this input port |
| * within an ordered vnet |
| * that arrived before this flit and is requesting the same output port. |
| */ |
| |
| bool |
| SwitchAllocator::send_allowed(int inport, int invc, int outport, int outvc) |
| { |
| // Check if outvc needed |
| // Check if credit needed (for multi-flit packet) |
| // Check if ordering violated (in ordered vnet) |
| |
| int vnet = get_vnet(invc); |
| bool has_outvc = (outvc != -1); |
| bool has_credit = false; |
| |
| if (!has_outvc) { |
| |
| // needs outvc |
| // this is only true for HEAD and HEAD_TAIL flits. |
| |
| if (m_output_unit[outport]->has_free_vc(vnet)) { |
| |
| has_outvc = true; |
| |
| // each VC has at least one buffer, |
| // so no need for additional credit check |
| has_credit = true; |
| } |
| } else { |
| has_credit = m_output_unit[outport]->has_credit(outvc); |
| } |
| |
| // cannot send if no outvc or no credit. |
| if (!has_outvc || !has_credit) |
| return false; |
| |
| |
| // protocol ordering check |
| if ((m_router->get_net_ptr())->isVNetOrdered(vnet)) { |
| |
| // enqueue time of this flit |
| Cycles t_enqueue_time = m_input_unit[inport]->get_enqueue_time(invc); |
| |
| // check if any other flit is ready for SA and for same output port |
| // and was enqueued before this flit |
| int vc_base = vnet*m_vc_per_vnet; |
| for (int vc_offset = 0; vc_offset < m_vc_per_vnet; vc_offset++) { |
| int temp_vc = vc_base + vc_offset; |
| if (m_input_unit[inport]->need_stage(temp_vc, SA_, |
| m_router->curCycle()) && |
| (m_input_unit[inport]->get_outport(temp_vc) == outport) && |
| (m_input_unit[inport]->get_enqueue_time(temp_vc) < |
| t_enqueue_time)) { |
| return false; |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| // Assign a free VC to the winner of the output port. |
| int |
| SwitchAllocator::vc_allocate(int outport, int inport, int invc) |
| { |
| // Select a free VC from the output port |
| int outvc = m_output_unit[outport]->select_free_vc(get_vnet(invc)); |
| |
| // has to get a valid VC since it checked before performing SA |
| assert(outvc != -1); |
| m_input_unit[inport]->grant_outvc(invc, outvc); |
| return outvc; |
| } |
| |
| // Wakeup the router next cycle to perform SA again |
| // if there are flits ready. |
| void |
| SwitchAllocator::check_for_wakeup() |
| { |
| Cycles nextCycle = m_router->curCycle() + Cycles(1); |
| |
| for (int i = 0; i < m_num_inports; i++) { |
| for (int j = 0; j < m_num_vcs; j++) { |
| if (m_input_unit[i]->need_stage(j, SA_, nextCycle)) { |
| m_router->schedule_wakeup(Cycles(1)); |
| return; |
| } |
| } |
| } |
| } |
| |
| int |
| SwitchAllocator::get_vnet(int invc) |
| { |
| int vnet = invc/m_vc_per_vnet; |
| assert(vnet < m_router->get_num_vnets()); |
| return vnet; |
| } |
| |
| |
| // Clear the request vector within the allocator at end of SA-II. |
| // Was populated by SA-I. |
| void |
| SwitchAllocator::clear_request_vector() |
| { |
| for (int i = 0; i < m_num_outports; i++) { |
| for (int j = 0; j < m_num_inports; j++) { |
| m_port_requests[i][j] = false; |
| } |
| } |
| } |
| |
| void |
| SwitchAllocator::resetStats() |
| { |
| m_input_arbiter_activity = 0; |
| m_output_arbiter_activity = 0; |
| } |