// -*- c++ -*-
//
// cache.cc:
// (little-endian access only)
//

using namespace std;
#include <iostream>
#include <cstdlib>
#include <cassert>
#include "cache.h"

//
// constructor
//
cache::cache(int nways, int nbits_sets, int nbits_block_size)
{
    mem = NULL;
    busy_count = 0;

    // setup cache parameters
    num_of_ways = nways;

    nbits_of_sets = nbits_sets;
    num_of_sets = 1 << nbits_of_sets;

    nbits_of_block_size = nbits_block_size;
    block_size = 1 << nbits_of_block_size;

    // T.B.D.
    // setup various data structures for manipulating cache
}

//
// destructor
//
cache::~cache()
{
}

//
// cache class member functions
//

void
cache::release_request_port()
{
    request_port.clear();
}

void
cache::release_reply_port()
{
    reply_port.clear();
}

bool
cache::has_data(sim_addr addr)
{
    // T.B.D.

//    return false;	// (temporarily) always miss
    return true;	// (temporarily) always hit
}

bool
cache::has_copyback_data(sim_addr addr)
{
    // T.B.D.

    return false;	// (temporarily) no need copyback for replacement
//    return true;	// (temporarily) always requires copyback for replacement
}

void
cache::alloc(sim_addr addr)
{
    // T.B.D.
}

void
cache::alloc_with_replacement(sim_addr addr)
{
    // T.B.D.
}

void
cache::connect_memory(memory *mem)
{
    this->mem = mem;
}

bool
cache::is_ready()
{
    return (busy_count != 0 || request_port.is_full())? false: true;
}

bool
cache::is_done()
{
    return (busy_count != 0 || reply_port.is_empty())? false: true;
}

void
cache::request_read_byte(sim_addr addr)
{
    packet packt;
    packt.ctype = command_read_request;
    packt.dtype = data_byte;
    packt.addr = addr;

    request_port.put(packt);
}

void
cache::request_read_hword(sim_addr addr)
{
    packet packt;
    packt.ctype = command_read_request;
    packt.dtype = data_hword;
    packt.addr = addr;

    request_port.put(packt);
}

void
cache::request_read_word(sim_addr addr)
{
    packet packt;
    packt.ctype = command_read_request;
    packt.dtype = data_word;
    packt.addr = addr;

    request_port.put(packt);
}

void
cache::request_read_dword(sim_addr addr)
{
    packet packt;
    packt.ctype = command_read_request;
    packt.dtype = data_dword;
    packt.addr = addr;

    request_port.put(packt);
}

void
cache::reply_read_byte(sim_addr addr, sim_byte &val)
{
    packet &packt = reply_port.get();
    assert(packt.ctype == command_read_reply);
    assert(packt.dtype == data_byte);
    assert(packt.addr == addr);
    val = packt.data.b;

    release_reply_port();
}

void
cache::reply_read_hword(sim_addr addr, sim_hword &val)
{
    packet &packt = reply_port.get();
    assert(packt.ctype == command_read_reply);
    assert(packt.dtype == data_hword);
    assert(packt.addr == addr);
    val = packt.data.h;

    release_reply_port();
}

void
cache::reply_read_word(sim_addr addr, sim_word &val)
{
    packet &packt = reply_port.get();
    assert(packt.ctype == command_read_reply);
    assert(packt.dtype == data_word);
    assert(packt.addr == addr);
    val = packt.data.w;

    release_reply_port();
}

void
cache::reply_read_dword(sim_addr addr, sim_dword &val)
{
    packet &packt = reply_port.get();
    assert(packt.ctype == command_read_reply);
    assert(packt.dtype == data_dword);
    assert(packt.addr == addr);
    val = packt.data.d;

    release_reply_port();
}

void
cache::request_write_byte(sim_addr addr, sim_byte val)
{
    packet packt;
    packt.ctype = command_write_request;
    packt.dtype = data_byte;
    packt.addr = addr;
    packt.data.h = val;

    request_port.put(packt);
}

void
cache::request_write_hword(sim_addr addr, sim_hword val)
{
    packet packt;
    packt.ctype = command_write_request;
    packt.dtype = data_hword;
    packt.addr = addr;
    packt.data.b = val;

    request_port.put(packt);
}

void
cache::request_write_word(sim_addr addr, sim_word val)
{
    packet packt;
    packt.ctype = command_write_request;
    packt.dtype = data_word;
    packt.addr = addr;
    packt.data.w = val;

    request_port.put(packt);
}

void
cache::request_write_dword(sim_addr addr, sim_dword val)
{
    packet packt;
    packt.ctype = command_write_request;
    packt.dtype = data_dword;
    packt.addr = addr;
    packt.data.d = val;

    request_port.put(packt);
}

void
cache::reply_write_byte(sim_addr addr)
{
    packet &packt = reply_port.get();
    assert(packt.ctype == command_write_reply);
    assert(packt.dtype == data_byte);
    assert(packt.addr == addr);

    release_reply_port();
}

void
cache::reply_write_hword(sim_addr addr)
{
    packet &packt = reply_port.get();
    assert(packt.ctype == command_write_reply);
    assert(packt.dtype == data_hword);
    assert(packt.addr == addr);

    release_reply_port();
}

void
cache::reply_write_word(sim_addr addr)
{
    packet &packt = reply_port.get();
    assert(packt.ctype == command_write_reply);
    assert(packt.dtype == data_word);
    assert(packt.addr == addr);

    release_reply_port();
}

void
cache::reply_write_dword(sim_addr addr)
{
    packet &packt = reply_port.get();
    assert(packt.ctype == command_write_reply);
    assert(packt.dtype == data_dword);
    assert(packt.addr == addr);

    release_reply_port();
}

void
cache::update()
{
    if (--busy_count > 0)		// cache is busy
	return;

    assert(busy_count <= 0);
    busy_count = 0;

    if (reply_port.is_full())		// reply port is full, so cannot proceed
	return;

    packet &req_packt = request_port.get();
    packet &reply_packt = reply_port.get();

    reply_packt = req_packt;

    switch (req_packt.ctype) {
    case command_null:
	break;

    case command_read_request:
	reply_packt.ctype = command_read_reply;
	if (has_data(req_packt.addr)) {		// cache-hit
	    busy_count = cache_read_delay;
	}
	else if (has_copyback_data(req_packt.addr)) { // write-back and read-in
	    alloc_with_replacement(req_packt.addr);
	    busy_count = cache_read_delay + memory_read_delay + memory_write_delay;
	}
	else {					// read-in
	    alloc(req_packt.addr);
	    busy_count = cache_read_delay + memory_read_delay;
	}

	switch (req_packt.dtype) {
	case data_byte:
	    reply_packt.data.b = mem->read_byte(req_packt.addr);
	    break;
	case data_hword:
	    reply_packt.data.h = mem->read_hword(req_packt.addr);
	    break;
	case data_word:
	    reply_packt.data.w = mem->read_word(req_packt.addr);
	    break;
	case data_dword:
	    reply_packt.data.d = mem->read_dword(req_packt.addr);
	    break;
	default:
	    cerr << "***** illegal data type *****" << endl;
	    break;
	}

	release_request_port();
	break;

    case command_write_request:
	reply_packt.ctype = command_write_reply;
	if (has_data(req_packt.addr)) {		// cache-hit
	    busy_count = cache_write_delay;
	}
	else if (has_copyback_data(req_packt.addr)) { // write-back and read-in
	    alloc_with_replacement(req_packt.addr);
	    busy_count = cache_write_delay + memory_read_delay + memory_write_delay;
	}
	else {					// read-in
	    alloc(req_packt.addr);
	    busy_count = cache_write_delay + memory_read_delay;
	}

	switch (req_packt.dtype) {
	case data_byte:
	    mem->write_byte(req_packt.addr, req_packt.data.b);
	    break;
	case data_hword:
	    mem->write_hword(req_packt.addr, req_packt.data.h);
	    break;
	case data_word:
	    mem->write_word(req_packt.addr, req_packt.data.w);
	    break;
	case data_dword:
	    mem->write_dword(req_packt.addr, req_packt.data.d);
	    break;
	default:
	    cerr << "***** illegal data type *****" << endl;
	    break;
	}

	release_request_port();
	break;

    case command_read_reply:
    case command_write_reply:
	cerr << "***** found reply packet in request port *****" << endl;
	break;

    default:
	cerr << "***** illegal command packet *****" << endl;
	break;
    }
}

void
cache::print(sim_addr addr)
{
    // print status of one cache line specified by 'addr'
}

void
cache::print_all()
{
    // print status of all cache lines
}

#if 0
//
// test code
//
int main(int argc, char *argv[])
{

    return 0;
}
#endif

// end of cache.cc