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

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

//
// constructor
//
cache::cache(int n_ways, int nbits_indexes, int nbits_block_size)
{
    this->mem = NULL;
    this->busy_count = 0;

    // setup cache parameters
    this->n_ways = n_ways;

    this->nbits_indexes = nbits_indexes;
    this->n_indexes = 1 << nbits_indexes;

    this->nbits_block_size = nbits_block_size;
    this->block_size = 1 << nbits_block_size;

    // rest all performance counters
    n_accesses = n_misses = n_replacements = 0;
}

//
// destructor
//
cache::~cache()
{
    // to be implemented (if necessary)
}

//
// cache class member functions
//

bool
cache::has_data(sim_addr addr)
{
    return true;	// to be implemented
}

int
cache::victim_way(sim_addr addr)
{
    return 0;           // to be implemented
}

bool
cache::victim_needs_copyback(sim_addr addr, int way)
{
    return false;	// to be implemented
}

void
cache::fill_block(sim_addr addr, int way)
{
    // to be implemented
}

void
cache::access_block(sim_addr addr, bool write_flag)
{
    // to be implemented
}

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;
    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;
    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;
    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;
    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);
    assert(packt.dtype == data_byte);
    assert(packt.addr == addr);
    val = packt.data.b;

    reply_port.clear();
}

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

    reply_port.clear();
}

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

    reply_port.clear();
}

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

    reply_port.clear();
}

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

    request_port.put(packt);
}

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

    request_port.put(packt);
}

void
cache::request_write_word(sim_addr addr, sim_word val)
{
    packet packt;
    packt.ctype = command_write;
    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;
    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);
    assert(packt.dtype == data_byte);
    assert(packt.addr == addr);

    reply_port.clear();
}

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

    reply_port.clear();
}

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

    reply_port.clear();
}

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

    reply_port.clear();
}

void
cache::cancel_request()
{
    if (!request_port.is_empty()) {
        packet &packt = request_port.get();
        cout << " cache request ("
             << hex << packt.addr << ") canceled" << endl;

        request_port.clear();           // delete request packet
    }

    busy_count = 0;                     // reset busy-counter
}

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

    assert(busy_count <= 0);
    busy_count = 0;                     // reset counter to 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:
        // nothing to do
	break;

    case command_read:
        if (!has_data(req_packt.addr)) {
            // cache miss

            cout << "cache miss (read, addr=" << hex << req_packt.addr << ")" << endl;

            int victim = victim_way(req_packt.addr);

            if (victim_needs_copyback(req_packt.addr, victim)) {
                // copyback is required for victim block
                // (needs memory write access)
                busy_count += memory_write_delay;

                n_replacements++;
            }

            // fill the block (cache line) of specified cache-way
            // (needs memory read access)
            busy_count += memory_read_delay;

            fill_block(req_packt.addr, victim);

            n_misses++;
        }

        n_accesses++;

        // setup reply packet
	reply_packt.ctype = command_read;
	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;
	}

        busy_count += cache_read_delay;

        access_block(req_packt.addr, false);

        request_port.clear();           // delete request packet
	break;

    case command_write:
        if (!has_data(req_packt.addr)) {
            // cache miss

            cout << "cache miss (write, addr=" << hex << req_packt.addr << ")" << endl;

            int victim = victim_way(req_packt.addr);

            if (victim_needs_copyback(req_packt.addr, victim)) {
                // copyback is required for victim block
                // (needs memory write access)
                busy_count += memory_write_delay;

                n_replacements++;
            }

            // fill block (cache line)
            // (needs memory read access)
            busy_count += memory_read_delay;

            fill_block(req_packt.addr, victim);

            n_misses++;
        }

        n_accesses++;

        // setup reply packet
	reply_packt.ctype = command_write;
	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;
	}

        busy_count += cache_write_delay;

        access_block(req_packt.addr, true);

        request_port.clear();           // delete request packet
	break;

    default:
        assert(0);
	break;
    }
}

void
cache::print_statistics()
{
    // print cache statistics
    cout << dec << "cache statistics: ";
    cout << n_accesses << " accesses, ";
    cout << n_misses << " misses, ";
    cout << n_replacements << " replacements" << endl;

    cout << "cache miss ratio = " << (double)n_misses/n_accesses*100
         << " %" << endl;
}

void
cache::print_set(int index_addr)
{
    // to be implemented (if necessary)
}

void
cache::print_all()
{
    // print status of all cache lines
    for (int i = 0; i < n_indexes; i++)
        print_set(i);
}

// end of cache.cc