diff -r 6dc9ab9b2294 -r 93f0e3b78f2d src/mem/ruby/network/simple/PerfectSwitch.cc --- a/src/mem/ruby/network/simple/PerfectSwitch.cc Fri Dec 23 08:43:18 2016 -0600 +++ b/src/mem/ruby/network/simple/PerfectSwitch.cc Fri Dec 23 09:03:23 2016 -0600 @@ -61,8 +61,25 @@ { m_network_ptr = network_ptr; - for (int i = 0;i < m_virtual_networks;++i) { + for (int i = 0; i < m_virtual_networks; ++i) { m_pending_message_count.push_back(0); + + vector> temp_bool; + m_prio_batches.push_back(temp_bool); + + vector temp_int; + m_prio_round_robin_start.push_back(temp_int); + + for (int j = 0; j < m_out.size(); j++) { + vector inner_temp; + m_prio_batches[i].push_back(inner_temp); + for (int k = 0; k < m_in.size(); k++) { + m_prio_batches[i][j].push_back(true); + } + + m_prio_round_robin_start[i].push_back(-1); + } + } } @@ -94,6 +111,11 @@ // Add to routing table m_out.push_back(out); m_routing_table.push_back(routing_table_entry); + + // Verify that link order, routing table, and output port vectors + // remain the same size + assert(m_link_order.size() == m_routing_table.size()); + assert(m_link_order.size() == m_out.size()); } PerfectSwitch::~PerfectSwitch() @@ -101,181 +123,258 @@ } void -PerfectSwitch::operateVnet(int vnet) +PerfectSwitch::startPrioBatch(int vnet, int outgoing) { - // This is for round-robin scheduling - int incoming = m_round_robin_start; - m_round_robin_start++; - if (m_round_robin_start >= m_in.size()) { - m_round_robin_start = 0; - } - - if (m_pending_message_count[vnet] > 0) { - // for all input ports, use round robin scheduling - for (int counter = 0; counter < m_in.size(); counter++) { - // Round robin scheduling - incoming++; - if (incoming >= m_in.size()) { - incoming = 0; - } - - // Is there a message waiting? - if (m_in[incoming].size() <= vnet) { - continue; - } - - MessageBuffer *buffer = m_in[incoming][vnet]; - if (buffer == nullptr) { - continue; - } - - operateMessageBuffer(buffer, incoming, vnet); - } + for (int incoming = 0; incoming < m_prio_batches[vnet][outgoing].size(); + incoming++) { + m_prio_batches[vnet][outgoing][incoming] = true; } } void -PerfectSwitch::operateMessageBuffer(MessageBuffer *buffer, int incoming, - int vnet) +PerfectSwitch::checkStartNewPrioBatch(int vnet, int outgoing) { - MsgPtr msg_ptr; - Message *net_msg_ptr = NULL; + bool step_batch = true; + for (int incoming = 0; + incoming < m_prio_batches[vnet][outgoing].size(); + incoming++) { + if (m_prio_batches[vnet][outgoing][incoming]) { + step_batch = false; + break; + } + } + if (step_batch) { + startPrioBatch(vnet, outgoing); + m_prio_round_robin_start[vnet][outgoing] = -1; + } +} - // temporary vectors to store the routing results - vector output_links; - vector output_link_destinations; - Tick current_time = m_switch->clockEdge(); +void +PerfectSwitch::operateVnet(int vnet) +{ + if (m_pending_message_count[vnet] > 0) { + DPRINTF(RubyNetwork, "Operating vnet %d:\n", vnet); - while (buffer->isReady(current_time)) { - DPRINTF(RubyNetwork, "incoming: %d\n", incoming); + // Since this is a "perfect" switch, it should issue all inports that + // it can until they are either empty, or they get blocked on full + // outports. Loop through testing switch traversals until no inport is + // able to issue further requests to any outports. + // The done_issuing bool tracks whether the switch has finished: + bool done_issuing = false; + Tick current_time = m_switch->clockEdge(); + while (!done_issuing) { + // Assume done_issuing until some inport issues to some outport + done_issuing = true; - // Peek at message - msg_ptr = buffer->peekMsgPtr(); - net_msg_ptr = msg_ptr.get(); - DPRINTF(RubyNetwork, "Message: %s\n", (*net_msg_ptr)); - - output_links.clear(); - output_link_destinations.clear(); - NetDest msg_dsts = net_msg_ptr->getDestination(); - - // Unfortunately, the token-protocol sends some - // zero-destination messages, so this assert isn't valid - // assert(msg_dsts.count() > 0); - - assert(m_link_order.size() == m_routing_table.size()); - assert(m_link_order.size() == m_out.size()); - - if (m_network_ptr->getAdaptiveRouting()) { - if (m_network_ptr->isVNetOrdered(vnet)) { - // Don't adaptively route - for (int out = 0; out < m_out.size(); out++) { - m_link_order[out].m_link = out; - m_link_order[out].m_value = 0; - } - } else { - // Find how clogged each link is - for (int out = 0; out < m_out.size(); out++) { - int out_queue_length = 0; - for (int v = 0; v < m_virtual_networks; v++) { - out_queue_length += m_out[out][v]->getSize(current_time); + // For each inport, queue its active, prioritized requests for + // the desired outports + vector> out_requested_ins; + for (int outport = 0; outport < m_out.size(); outport++) { + vector outport_req_ins; + out_requested_ins.push_back(outport_req_ins); + } + for (int incoming = 0; incoming < m_in.size(); incoming++) { + if (m_in[incoming].size() <= vnet || + m_in[incoming][vnet] == nullptr || + !m_in[incoming][vnet]->isReady(current_time)) { + // This inport doesn't have a buffer or is not ready, so + // it should be cleared from all outport batches + for (int outport = 0; outport < m_out.size(); outport++) { + m_prio_batches[vnet][outport][incoming] = false; } - int value = - (out_queue_length << 8) | - random_mt.random(0, 0xff); - m_link_order[out].m_link = out; - m_link_order[out].m_value = value; + continue; } - // Look at the most empty link first - sort(m_link_order.begin(), m_link_order.end()); + MsgPtr msg_ptr = m_in[incoming][vnet]->peekMsgPtr(); + Message *net_msg_ptr = msg_ptr.get(); + NetDest msg_dsts = net_msg_ptr->getDestination(); + + if (m_network_ptr->getAdaptiveRouting()) { + if (m_network_ptr->isVNetOrdered(vnet)) { + // Don't adaptively route + for (int out = 0; out < m_out.size(); out++) { + m_link_order[out].m_link = out; + m_link_order[out].m_value = 0; + } + } else { + // Find how clogged each link is + for (int out = 0; out < m_out.size(); out++) { + int out_queue_length = 0; + for (int v = 0; v < m_virtual_networks; v++) { + out_queue_length += + m_out[out][v]->getSize(current_time); + } + int value = + (out_queue_length << 8) | + random_mt.random(0, 0xff); + m_link_order[out].m_link = out; + m_link_order[out].m_value = value; + } + + // Look at the most empty link first + sort(m_link_order.begin(), m_link_order.end()); + } + } + + // Check each routing table entry to find the requested + // outports for this inport's message + for (int i = 0; i < m_routing_table.size(); i++) { + int link = m_link_order[i].m_link; + NetDest dst = m_routing_table[link]; + if (msg_dsts.intersectionIsNotEmpty(dst)) { + // Only add the inport if it is part of the + // current batch for this outport + if (m_prio_batches[vnet][link][incoming]) { + out_requested_ins[link].push_back(incoming); + DPRINTF(RubyNetwork, " [%d] in %d wants out %d\n", + vnet, incoming, link); + } + // Remove this dst set from the full set of dsts to + // ensure only one message gets sent to it + msg_dsts.removeNetDest(dst); + } else { + // This inport missed its chance to be part of the + // batch, so remove it + m_prio_batches[vnet][link][incoming] = false; + } + } + } + + // Service requested outport allocations + for (int outgoing = 0; outgoing < m_out.size(); outgoing++) { + if (out_requested_ins[outgoing].size() == 0) { + checkStartNewPrioBatch(vnet, outgoing); + continue; + } + + // Per-output-port arbitration: Set up per-batch round robin + // input port prioritization + int rr_leader = m_prio_round_robin_start[vnet][outgoing]; + if (rr_leader == -1) { + int index = random_mt.random(0, + (int) out_requested_ins[outgoing].size() - 1); + rr_leader = out_requested_ins[outgoing][index]; + m_prio_round_robin_start[vnet][outgoing] = rr_leader; + DPRINTF(RubyNetwork, " [%d] Starting batch with %d " + "inports, index %d, leader %d\n", vnet, + out_requested_ins[outgoing].size(), index, + rr_leader); + } + vector ordered_out_reqs; + for (int i = 0; i < out_requested_ins[outgoing].size(); i++) { + if (out_requested_ins[outgoing][i] >= rr_leader) { + ordered_out_reqs.push_back( + out_requested_ins[outgoing][i]); + } + } + for (int i = 0; i < out_requested_ins[outgoing].size(); i++) { + if (out_requested_ins[outgoing][i] < rr_leader) { + ordered_out_reqs.push_back( + out_requested_ins[outgoing][i]); + } else { + break; + } + } + + // NOTE: At this point, ordered_out_reqs contains all inports + // that are requesting this outport AND are in the current + // batch. Further, it is ordered according to the round robin + // priority start, so requests can be serviced from the head + // to tail in order to meet assured access arbitration needs + + // Per-output-port switch traversal: For each inport request to + // this outport, try moving messages from inports to outports + // until buffers fill + for (int i = 0; i < ordered_out_reqs.size(); i++) { + int incoming = ordered_out_reqs[i]; + + if (m_out[outgoing][vnet]->areNSlotsAvailable(1, + current_time)) { + // Sanity checks + assert(m_prio_batches[vnet][outgoing][incoming]); + + // Dequeue message from inport + MsgPtr msg_ptr = m_in[incoming][vnet]->peekMsgPtr(); + Message *net_msg_ptr = msg_ptr.get(); + NetDest &msg_dsts = net_msg_ptr->getDestination(); + DPRINTF(RubyNetwork, + " [%d] Original msg NetDest: %s\n", vnet, + msg_dsts); + + MsgPtr msg_to_send = msg_ptr; + + // Handle multi/broadcast messages by copying the + // inport's head message and adjusting destinations + NetDest link_dsts = m_routing_table[outgoing]; + if (!link_dsts.isSuperset(msg_dsts)) { + // Since there are more message destinations than + // will be serviced by this message, we need to + // replicate the message to send on other links + + // Clone the inport's message + msg_to_send = msg_ptr->clone(); + + // Set the replicated message's destinations on + // the other end of this outgoing link + msg_to_send.get()->getDestination() = + msg_dsts.AND(link_dsts); + + // Remove the replicated message's destinations + // from the original message's destinations + msg_dsts.removeNetDest(link_dsts); + DPRINTF(RubyNetwork, " [%d] Multicast msg from in " + "%d to out %d... Remaining NetDest: %s\n", + vnet, incoming, outgoing, msg_dsts); + } else { + m_in[incoming][vnet]->dequeue(current_time); + + // Remove the inport from the outport's batch + m_prio_batches[vnet][outgoing][incoming] = false; + + // Decrement the vnet's pending messages + m_pending_message_count[vnet]--; + } + + DPRINTF(RubyNetwork, " [%d] Enqueue msg from in %d to " + "out %d. NetDest: %s\n", vnet, incoming, + outgoing, msg_to_send.get()->getDestination()); + + // Enqueue the message in the outport + m_out[outgoing][vnet]->enqueue(msg_to_send, + current_time, + m_switch->cyclesToTicks(Cycles(1))); + + // Indicate that we were able to issue, so the switch + // should try cycling ports again before the end of + // the cycle + done_issuing = false; + } else { + break; + } + } + + // Check whether the batch has completed, so a new one + // should be started + checkStartNewPrioBatch(vnet, outgoing); } } - for (int i = 0; i < m_routing_table.size(); i++) { - // pick the next link to look at - int link = m_link_order[i].m_link; - NetDest dst = m_routing_table[link]; - DPRINTF(RubyNetwork, "dst: %s\n", dst); - - if (!msg_dsts.intersectionIsNotEmpty(dst)) - continue; - - // Remember what link we're using - output_links.push_back(link); - - // Need to remember which destinations need this message in - // another vector. This Set is the intersection of the - // routing_table entry and the current destination set. The - // intersection must not be empty, since we are inside "if" - output_link_destinations.push_back(msg_dsts.AND(dst)); - - // Next, we update the msg_destination not to include - // those nodes that were already handled by this link - msg_dsts.removeNetDest(dst); + // If there are still pending messages for this vnet, need to + // schedule an event so the switch can try again in the next cycle + if (m_pending_message_count[vnet] > 0) { + scheduleEvent(Cycles(1)); } - - assert(msg_dsts.count() == 0); - - // Check for resources - for all outgoing queues - bool enough = true; - for (int i = 0; i < output_links.size(); i++) { - int outgoing = output_links[i]; - - if (!m_out[outgoing][vnet]->areNSlotsAvailable(1, current_time)) - enough = false; - - DPRINTF(RubyNetwork, "Checking if node is blocked ..." - "outgoing: %d, vnet: %d, enough: %d\n", - outgoing, vnet, enough); - } - - // There were not enough resources - if (!enough) { - scheduleEvent(Cycles(1)); - DPRINTF(RubyNetwork, "Can't deliver message since a node " - "is blocked\n"); - DPRINTF(RubyNetwork, "Message: %s\n", (*net_msg_ptr)); - break; // go to next incoming port - } - - MsgPtr unmodified_msg_ptr; - - if (output_links.size() > 1) { - // If we are sending this message down more than one link - // (size>1), we need to make a copy of the message so each - // branch can have a different internal destination we need - // to create an unmodified MsgPtr because the MessageBuffer - // enqueue func will modify the message - - // This magic line creates a private copy of the message - unmodified_msg_ptr = msg_ptr->clone(); - } - - // Dequeue msg - buffer->dequeue(current_time); - m_pending_message_count[vnet]--; - - // Enqueue it - for all outgoing queues - for (int i=0; i 0) { - // create a private copy of the unmodified message - msg_ptr = unmodified_msg_ptr->clone(); + } else { + // There are no pending messages on this vnet, so prio batch should + // be completely cleared. Verify that this is true. + for (int outgoing = 0; outgoing < m_out.size(); outgoing++) { + for (int incoming = 0; + incoming < m_prio_batches[vnet][outgoing].size(); + incoming++) { + if (!m_prio_batches[vnet][outgoing][incoming]) { + panic("Invalid batch state!: No messages, but not reset"); + } } - - // Change the internal destination set of the message so it - // knows which destinations this link is responsible for. - net_msg_ptr = msg_ptr.get(); - net_msg_ptr->getDestination() = output_link_destinations[i]; - - // Enqeue msg - DPRINTF(RubyNetwork, "Enqueuing net msg from " - "inport[%d][%d] to outport [%d][%d].\n", - incoming, vnet, outgoing, vnet); - - m_out[outgoing][vnet]->enqueue(msg_ptr, current_time, - m_switch->cyclesToTicks(Cycles(1))); } } } @@ -326,3 +425,43 @@ { out << "[PerfectSwitch " << m_switch_id << "]"; } + +void +PerfectSwitch::dumpState(std::ostream& out) const +{ + Tick current_time = m_switch->clockEdge(); + + out << "[PerfectSwitch " << m_switch_id << "]: Dumping state..." << endl; + out << " Virtual nets:" << endl; + for (int vnet = 0; vnet < m_virtual_networks; vnet++) { + out << " Vnet: " << vnet << endl; + for (int incoming = 0; incoming < m_in.size(); incoming++) { + if (vnet < m_in[incoming].size() && m_in[incoming][vnet]) { + out << " In " << incoming << " ready: " + << (m_in[incoming][vnet]->isReady(current_time) ? + "true" : "false") + << ", full: " + << (m_in[incoming][vnet]->areNSlotsAvailable(1, + current_time) ? "false" : "true") + << endl; + } + } + + for (int outgoing = 0; outgoing < m_out.size(); outgoing++) { + if (vnet < m_out[outgoing].size() && m_out[outgoing][vnet]) { + out << " Out " << outgoing << " ready: " + << (m_out[outgoing][vnet]->isReady(current_time) ? + "true" : "false") + << ", full: " + << (m_out[outgoing][vnet]->areNSlotsAvailable(1, + current_time) ? "false" : "true") + << ", prio:"; + for (int incoming = 0; incoming < m_in.size(); incoming++) { + out << " " << m_prio_batches[vnet][outgoing][incoming]; + } + out << endl; + } + } + out << endl; + } +} diff -r 6dc9ab9b2294 -r 93f0e3b78f2d src/mem/ruby/network/simple/PerfectSwitch.hh --- a/src/mem/ruby/network/simple/PerfectSwitch.hh Fri Dec 23 08:43:18 2016 -0600 +++ b/src/mem/ruby/network/simple/PerfectSwitch.hh Fri Dec 23 09:03:23 2016 -0600 @@ -78,14 +78,16 @@ void clearStats(); void collateStats(); void print(std::ostream& out) const; + void dumpState(std::ostream& out) const; private: // Private copy constructor and assignment operator PerfectSwitch(const PerfectSwitch& obj); PerfectSwitch& operator=(const PerfectSwitch& obj); + void startPrioBatch(int vnet, int outgoing); + void checkStartNewPrioBatch(int vnet, int outgoing); void operateVnet(int vnet); - void operateMessageBuffer(MessageBuffer *b, int incoming, int vnet); const SwitchID m_switch_id; Switch * const m_switch; @@ -103,6 +105,23 @@ SimpleNetwork* m_network_ptr; std::vector m_pending_message_count; + + /** + * For each virtual network, maintain a vector of bits that indicates + * whether each input port is allowed to issue in this cycle. These + * vectors are used to ensure, precisely, that no input port with ready + * messages is allowed to issue twice before any other input port with + * ready messages is allowed to issue once. This is used to enforce + * bandwidth fairness when buffers are near saturation. + */ + std::vector>> m_prio_batches; + + /** + * For each virtual network, maintain a vector for the round robin + * priority input port to each output port. The round robin start port + * should be cycled each time that a priority batch completes. + */ + std::vector> m_prio_round_robin_start; }; inline std::ostream&