blob: ed97b216c4efd5e6b9aa8c209179ab236a14f6c2 [file] [log] [blame]
/*
* 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");
}
}