| /* |
| * Copyright (c) 1999-2008 Mark D. Hill and David A. Wood |
| * 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. |
| */ |
| |
| /*------------------------------------------------------------------------*/ |
| /* Includes */ |
| /*------------------------------------------------------------------------*/ |
| |
| #include <map> |
| |
| #include "mem/ruby/storebuffer/hfa.hh" |
| #include "mem/ruby/storebuffer/storebuffer.hh" |
| #include "mem/ruby/common/Global.hh" |
| |
| #if RUBY_TSO_CHECKER |
| #include "TsoChecker.hh" |
| #endif |
| |
| #define SYSTEM_EXIT ASSERT(0) |
| |
| |
| // global map of request id_s to map them back to storebuffer pointers |
| map <uint64_t, StoreBuffer *> request_map; |
| |
| #if RUBY_TSO_CHECKER |
| Tso::TsoChecker * g_tsoChecker; |
| #endif |
| |
| void hit(int64_t id) { |
| if (request_map.find(id) == request_map.end()) { |
| ERROR_OUT("Request ID not found in the map"); |
| DEBUG_EXPR(STOREBUFFER_COMP, MedPrio, id); |
| ASSERT(0); |
| } |
| else { |
| request_map[id]->complete(id); |
| request_map.erase(id); |
| } |
| } |
| |
| |
| //***************************************************************************************** |
| StoreBuffer::StoreBuffer(uint32 id, uint32 block_bits, int storebuffer_size) { |
| #if RUBY_TSO_CHECKER |
| if (id == 0) { |
| g_tsoChecker = new Tso::TsoChecker(); |
| g_tsoChecker->init(64); |
| } |
| #endif |
| iseq = 0; |
| tso_iseq = 0; |
| char name [] = "Sequencer_"; |
| char port_name [13]; |
| sprintf(port_name, "%s%d", name, id); |
| m_port = libruby_get_port(port_name, hit); |
| m_hit_callback = NULL; |
| ASSERT(storebuffer_size >= 0); |
| m_storebuffer_size = storebuffer_size; |
| m_id = id; |
| m_block_size = 1 << block_bits; |
| m_block_mask = ~(m_block_size - 1); |
| m_buffer_size = 0; |
| m_use_storebuffer = false; |
| m_storebuffer_full = false; |
| m_storebuffer_flushing = false; |
| m_stalled_issue = true; |
| if(m_storebuffer_size > 0){ |
| m_use_storebuffer = true; |
| } |
| |
| #ifdef DEBUG_WRITE_BUFFER |
| DEBUG_OUT("*******storebuffer_t::Using Write Buffer? %d\n",m_use_storebuffer); |
| #endif |
| } |
| |
| //****************************************************************************************** |
| StoreBuffer::~StoreBuffer(){ |
| #if RUBY_TSO_CHECKER |
| if (m_id == 0) { |
| delete g_tsoChecker; |
| } |
| #endif |
| } |
| |
| //***************************************************************************************************** |
| void StoreBuffer::registerHitCallback(void (*hit_callback)(int64_t request_id)) { |
| assert(m_hit_callback == NULL); // can't assign hit_callback twice |
| m_hit_callback = hit_callback; |
| } |
| |
| |
| //***************************************************************************************************** |
| void StoreBuffer::addToStoreBuffer(struct RubyRequest request){ |
| if(m_use_storebuffer){ |
| #ifdef DEBUG_WRITE_BUFFER |
| DEBUG_OUT("\n***StoreBuffer: addToStoreBuffer BEGIN, contents:\n"); |
| DEBUG_OUT("\n"); |
| #endif |
| |
| #ifdef DEBUG_WRITE_BUFFER |
| DEBUG_OUT("\t INSERTING new request\n"); |
| #endif |
| |
| |
| buffer.push_front(SBEntry(request, NULL)); |
| |
| m_buffer_size++; |
| |
| if (m_buffer_size >= m_storebuffer_size) { |
| m_storebuffer_full = true; |
| } |
| else if (m_stalled_issue) { |
| m_stalled_issue = false; |
| issueNextStore(); |
| } |
| |
| iseq++; |
| |
| #ifdef DEBUG_WRITE_BUFFER |
| DEBUG_OUT("***StoreBuffer: addToStoreBuffer END, contents:\n"); |
| DEBUG_OUT("\n"); |
| #endif |
| } //end if(m_use_storebuffer) |
| else { |
| // make request to libruby |
| uint64_t id = libruby_issue_request(m_port, request); |
| if (request_map.find(id) != request_map.end()) { |
| ERROR_OUT("Request ID is already in the map"); |
| DEBUG_EXPR(STOREBUFFER_COMP, MedPrio, id); |
| ASSERT(0); |
| } |
| else { |
| request_map.insert(make_pair(id, this)); |
| outstanding_requests.insert(make_pair(id, request)); |
| } |
| } |
| } |
| |
| |
| //***************************************************************************************************** |
| // Return value of -2 indicates that the load request was satisfied by the store buffer |
| // Return value of -3 indicates a partial match, so the load has to retry until NO_MATCH |
| // Alternatively we could satisfy the partial match, but tso gets complicated and more races |
| //***************************************************************************************************** |
| int64_t StoreBuffer::handleLoad(struct RubyRequest request) { |
| if (m_use_storebuffer) { |
| load_match match = checkForLoadHit(request); |
| if (match == FULL_MATCH) { |
| // fill data |
| returnMatchedData(request); |
| iseq++; |
| return -2; |
| } |
| else if (match == NO_MATCH) { |
| // make request to libruby and return the id |
| uint64_t id = libruby_issue_request(m_port, request); |
| if (request_map.find(id) != request_map.end()) { |
| ERROR_OUT("Request ID is already in the map"); |
| DEBUG_EXPR(STOREBUFFER_COMP, MedPrio, id); |
| ASSERT(0); |
| } |
| else { |
| request_map.insert(make_pair(id, this)); |
| outstanding_requests.insert(make_pair(id, request)); |
| } |
| iseq++; |
| return id; |
| } |
| else { // partial match |
| return -3; |
| } |
| } |
| else { |
| // make a request to ruby |
| return libruby_issue_request(m_port, request); |
| } |
| } |
| |
| |
| //***************************************************************************************************** |
| // This function will fill the data array if any match is found |
| //***************************************************************************************************** |
| load_match StoreBuffer::checkForLoadHit(struct RubyRequest request) { |
| if (m_use_storebuffer) { |
| physical_address_t physical_address = request.paddr; |
| int len = request.len; |
| |
| uint8_t * data = new uint8_t[64]; |
| memset(data, 0, 64); |
| for (int i = physical_address%64; i < len; i++) { |
| data[i] = 1; |
| } |
| |
| bool found = false; |
| physical_address_t lineaddr = physical_address & m_block_mask; |
| |
| // iterate over the buffer looking for hits |
| for (deque<struct SBEntry>::iterator it = buffer.begin(); it != buffer.end(); it++) { |
| if ((it->m_request.paddr & m_block_mask) == lineaddr) { |
| found = true; |
| for (int i = it->m_request.paddr%64; i < it->m_request.len; i++) { |
| data[i] = 0; |
| } |
| } |
| } |
| |
| // if any matching entry is found, determine if all the requested bytes have been matched |
| if (found) { |
| ASSERT(m_buffer_size > 0); |
| int unmatched_bytes = 0; |
| for (int i = physical_address%64; i < len; i++) { |
| unmatched_bytes = unmatched_bytes + data[i]; |
| } |
| if (unmatched_bytes == 0) { |
| delete data; |
| return FULL_MATCH; |
| } |
| else { |
| delete data; |
| return PARTIAL_MATCH; |
| } |
| } |
| else { |
| delete data; |
| return NO_MATCH; |
| } |
| } // end of if (m_use_storebuffer) |
| else { |
| // this function should never be called if we are not using a store buffer |
| ERROR_OUT("checkForLoadHit called while write buffer is not in use"); |
| ASSERT(0); |
| } |
| } |
| |
| |
| //*************************************************************************************************** |
| void StoreBuffer::returnMatchedData(struct RubyRequest request) { |
| if (m_use_storebuffer) { |
| |
| uint8_t * data = new uint8_t[64]; |
| memset(data, 0, 64); |
| uint8_t * written = new uint8_t[64]; |
| memset(written, 0, 64); |
| |
| physical_address_t physical_address = request.paddr; |
| int len = request.len; |
| |
| ASSERT(checkForLoadHit(request) != NO_MATCH); |
| physical_address_t lineaddr = physical_address & m_block_mask; |
| bool found = false; |
| #if RUBY_TSO_CHECKER |
| Tso::TsoCheckerCmd * cmd; |
| #endif |
| deque<struct SBEntry>::iterator satisfying_store; |
| for (deque<struct SBEntry>::iterator it = buffer.begin(); it != buffer.end(); it++) { |
| if ((it->m_request.paddr & m_block_mask) == lineaddr) { |
| if (!found) { |
| found = true; |
| #if RUBY_TSO_CHECKER |
| satisfying_store = it; |
| cmd = new Tso::TsoCheckerCmd(m_id, // this thread id |
| iseq, // instruction sequence |
| ITYPE_LOAD, // is a store |
| MEM_LOAD_DATA, // commit |
| request.paddr, // the address |
| NULL, // and data |
| request.len, // and len |
| DSRC_STB, // shouldn't matter |
| libruby_get_time(), // macc: for store macc and time are the same and it |
| 0, // gobs |
| 0); |
| #endif |
| } |
| uint8_t * dataPtr = it->m_request.data; |
| int offset = it->m_request.paddr%64; |
| for (int i = offset; i < it->m_request.len; i++) { |
| if (!written[i]) { // don't overwrite data with earlier data |
| data[i] = dataPtr[i-offset]; |
| written[i] = 1; |
| } |
| } |
| } |
| } |
| |
| int i = physical_address%64; |
| for (int j = 0; (i < physical_address%64 + len) && (j < len); i++, j++) { |
| if (written[i]) { |
| request.data[j] = data[i]; |
| } |
| } |
| |
| #if RUBY_TSO_CHECKER |
| uint64_t tso_data = 0; |
| memcpy(&tso_data, request.data, request.len); |
| cmd->setData(tso_data); |
| |
| Tso::TsoCheckerCmd * adjust_cmd = satisfying_store->m_next_ptr; |
| if (adjust_cmd == NULL) { |
| adjust_cmd = cmd; |
| } |
| else { |
| while (adjust_cmd->getNext() != NULL) { |
| adjust_cmd = adjust_cmd->getNext(); |
| } |
| adjust_cmd->setNext(cmd); |
| } |
| #endif |
| |
| delete data; |
| delete written; |
| } |
| else { |
| ERROR_OUT("returnMatchedData called while write buffer is not in use"); |
| ASSERT(0); |
| } |
| } |
| |
| |
| //****************************************************************************************** |
| void StoreBuffer::flushStoreBuffer(){ |
| if (m_use_storebuffer) { |
| #ifdef DEBUG_WRITE_BUFFER |
| DEBUG_OUT("\n***StoreBuffer: flushStoreBuffer BEGIN, contents:\n"); |
| DEBUG_OUT("\n"); |
| #endif |
| |
| if(m_buffer_size > 0) { |
| m_storebuffer_flushing = true; // indicate that we are flushing |
| } |
| else { |
| m_storebuffer_flushing = false; |
| return; |
| } |
| } |
| else { |
| // do nothing |
| return; |
| } |
| } |
| |
| //**************************************************************************************** |
| void StoreBuffer::issueNextStore() { |
| SBEntry request = buffer.back(); |
| uint64_t id = libruby_issue_request(m_port, request.m_request); |
| if (request_map.find(id) != request_map.end()) { |
| assert(0); |
| } |
| else { |
| request_map.insert(make_pair(id, this)); |
| outstanding_requests.insert(make_pair(id, request.m_request)); |
| } |
| } |
| |
| //**************************************************************************************** |
| void StoreBuffer::complete(uint64_t id) { |
| if (m_use_storebuffer) { |
| ASSERT(outstanding_requests.find(id) != outstanding_requests.end()); |
| physical_address_t physical_address = outstanding_requests.find(id)->second.paddr; |
| RubyRequestType type = outstanding_requests.find(id)->second.type; |
| #ifdef DEBUG_WRITE_BUFFER |
| DEBUG_OUT("\n***StoreBuffer: complete BEGIN, contents:\n"); |
| DEBUG_OUT("\n"); |
| #endif |
| |
| if (type == RubyRequestType_ST) { |
| physical_address_t lineaddr = physical_address & m_block_mask; |
| |
| //Note fastpath hits are handled like regular requests - they must remove the WB entry! |
| if ( lineaddr != physical_address ) { |
| ERROR_OUT("error: StoreBuffer: ruby returns pa 0x%0llx which is not a cache line: 0x%0llx\n", physical_address, lineaddr ); |
| } |
| |
| SBEntry from_buffer = buffer.back(); |
| if (((from_buffer.m_request.paddr & m_block_mask) == lineaddr) && (from_buffer.m_request.type == type)) { |
| buffer.pop_back(); |
| m_buffer_size--; |
| ASSERT(m_buffer_size >= 0); |
| |
| #if RUBY_TSO_CHECKER |
| int len = outstanding_requests.find(id)->second.len; |
| uint64_t data = 0; |
| memcpy(&data, from_buffer.m_request.data, 4); |
| |
| cerr << m_id << " INSERTING STORE" << endl << flush; |
| // add to the tsoChecker |
| g_tsoChecker->input(m_id, // this thread id |
| (id & ISEQ_MASK), // instruction sequence |
| ITYPE_STORE, // is a store |
| MEM_STORE_COMMIT, // commit |
| physical_address, // the address |
| data, // and data |
| len, // and len |
| DSRC_STB, // shouldn't matter |
| libruby_get_time(), // macc |
| libruby_get_time(), // gobs |
| libruby_get_time()); // time |
| tso_iseq++; |
| |
| // also add the loads that are satisfied by this store |
| if (from_buffer.m_next_ptr != NULL) { |
| from_buffer.m_next_ptr->setGobs(libruby_get_time()); |
| g_tsoChecker->input(*(from_buffer.m_next_ptr)); |
| cerr << m_id << " INSERTING LOAD for STORE: " << from_buffer.m_next_ptr->getIseq() << endl << flush; |
| tso_iseq++; |
| Tso::TsoCheckerCmd * to_input = from_buffer.m_next_ptr->getNext(); |
| while (to_input != NULL) { |
| if (to_input->getGobs() == 0) { |
| to_input->setGobs(libruby_get_time()); |
| } |
| cerr << m_id << " INSERTING LOAD iseq for STORE: " << to_input->getIseq() << endl << flush; |
| g_tsoChecker->input(*to_input); |
| tso_iseq++; |
| to_input = to_input->getNext(); |
| } |
| } |
| #endif |
| // schedule the next request |
| if (m_buffer_size > 0) { |
| issueNextStore(); |
| } |
| else if (m_buffer_size == 0) { |
| m_storebuffer_flushing = false; |
| m_stalled_issue = true; |
| } |
| |
| m_storebuffer_full = false; |
| |
| } |
| else { |
| ERROR_OUT("[%d] error: StoreBuffer: at complete, address 0x%0llx not found.\n", m_id, lineaddr); |
| ERROR_OUT("StoreBuffer:: complete FAILS\n"); |
| ASSERT(0); |
| } |
| |
| #ifdef DEBUG_WRITE_BUFFER |
| DEBUG_OUT("***StoreBuffer: complete END, contents:\n"); |
| DEBUG_OUT("\n"); |
| #endif |
| } // end if (type == ST) |
| else if (type == RubyRequestType_LD) { |
| #if RUBY_TSO_CHECKER |
| RubyRequest request = outstanding_requests.find(id)->second; |
| uint64_t data = 0; |
| memcpy(&data, request.data, request.len); |
| |
| // add to the tsoChecker if in order, otherwise, find a place to put ourselves |
| if ((id & ISEQ_MASK) == tso_iseq) { |
| tso_iseq++; |
| cerr << m_id << " INSERTING LOAD" << endl << flush; |
| g_tsoChecker->input(m_id, // this thread id |
| (id & ISEQ_MASK), // instruction sequence |
| ITYPE_LOAD, // is a store |
| MEM_LOAD_DATA, // commit |
| request.paddr, // the address |
| data, // and data |
| request.len, // and len |
| DSRC_L2_MEMORY, // shouldn't matter DSRC_L1 |
| libruby_get_time(), // macc: for store macc and time are the same and it |
| libruby_get_time(), // macc |
| libruby_get_time()); // time |
| } |
| else { |
| Tso::TsoCheckerCmd * cmd; |
| cmd = new Tso::TsoCheckerCmd(m_id, // this thread id |
| (id & ISEQ_MASK), // instruction sequence |
| ITYPE_LOAD, // is a store |
| MEM_LOAD_DATA, // commit |
| request.paddr, // the address |
| data, // and data |
| request.len, // and len |
| DSRC_L2_MEMORY, // shouldn't matter DSRC_L1 |
| libruby_get_time(), // macc: for store macc and time are the same and it |
| libruby_get_time(), // macc |
| libruby_get_time()); // time |
| insertTsoLL(cmd); |
| } |
| #endif |
| m_hit_callback(id); |
| } |
| |
| // LD, ST or FETCH hit callback |
| outstanding_requests.erase(id); |
| |
| } // end if(m_use_storebuffer) |
| else { |
| m_hit_callback(id); |
| } |
| } |
| |
| #if RUBY_TSO_CHECKER |
| void StoreBuffer::insertTsoLL(Tso::TsoCheckerCmd * cmd) { |
| uint64_t count = cmd->getIseq(); |
| Tso::TsoCheckerCmd * current = NULL; |
| Tso::TsoCheckerCmd * previous = NULL; |
| deque<struct SBEntry>::reverse_iterator iter; |
| bool found = false; |
| for (iter = buffer.rbegin(); iter != buffer.rend(); ++ iter) { |
| if (iter->m_next_ptr != NULL) { |
| current = iter->m_next_ptr->getNext(); // initalize both to the beginning of the linked list |
| previous = current; |
| while (current != NULL) { |
| if (current->getIseq() > count) { |
| found = true; |
| break; |
| } |
| previous = current; |
| current = current->getNext(); |
| } |
| } |
| // break out if found a match, iterator should still point to the right SBEntry |
| if (found) { |
| break; |
| } |
| } |
| |
| // will insert at the end if not found |
| if (!found) { |
| buffer.front().m_next_ptr = cmd; |
| } |
| else if (current == previous) { |
| cerr << "INSERTING " << count << " BEFORE: " << iter->m_next_ptr->getIseq(); |
| Tso::TsoCheckerCmd * temp = iter->m_next_ptr; |
| iter->m_next_ptr = cmd; |
| cmd->setNext(temp); |
| } |
| else { |
| cerr << "INSERTING " << count << " BETWEEN: " << previous->getIseq() << " AND " << current->getIseq(); |
| cmd->setNext(current); |
| previous->setNext(cmd); |
| } |
| } |
| #endif |
| |
| //*************************************************************************************************** |
| void StoreBuffer::print( void ) |
| { |
| DEBUG_OUT("[%d] StoreBuffer: Total entries: %d Outstanding: %d\n", m_id, m_buffer_size); |
| |
| if(m_use_storebuffer){ |
| } |
| else{ |
| DEBUG_OUT("\t WRITE BUFFER NOT USED\n"); |
| } |
| } |
| |
| |
| |
| |