// -*- c++ -*-
//
// cache.h:
// base type cache
//
#ifndef	CACHE_H
#define	CACHE_H

using namespace std;
#include "sim.h"
#include "memory.h"

//
// cache class
//
class cache {

    //
    // constants
    //
    static const int memory_read_delay  = 10;	// memory read access delay
    static const int memory_write_delay = 10;	// memory write access delay

    static const int cache_read_delay  = 0;	// cache read access delay
                                                // (0: full-speed)
    static const int cache_write_delay = 0;	// cache write access delay
                                                // (0: full-speed)

    //
    // type definitions
    //
    enum command_type {
	command_null = 0,
	command_read,
	command_write,
    };

    enum data_type {
	data_null = 0,
	data_byte,
	data_hword,
	data_word,
	data_dword,
    };


    struct packet {
	command_type ctype;
	data_type dtype;
	sim_addr addr;
	union data {
	    sim_byte  b;
	    sim_hword h;
	    sim_word  w;
	    sim_dword d;
	} data;

	// member function
	packet() { clear(); }
	void clear() {
	    ctype = command_null;
	    dtype = data_null;
	    addr = 0;
	    data.d.h = data.d.l = 0;
	}
	bool is_valid() { return ctype != command_null? true: false; }
    };

    class port {
	packet packt;		// packet in port (only one packet contained)
    public:
	void put(packet &packt) { this->packt = packt; }
	packet &get() { return packt; }
	void clear() { packt.clear(); }
	bool is_full() { return packt.is_valid(); }
	bool is_empty() { return !packt.is_valid(); }
    };

    // T.B.D.
    // put here your necessary data structure definitions for manipulating cache


    //
    // member variables
    //
    memory *mem;		// connected memory

    port request_port;		// port for cache request (from cpu)
    port reply_port;		// port for cache reply (to cpu)

    int busy_count;		// busy-counter for current request processing


    // parameters
    int n_ways;		        // cache associativity
    int nbits_indexes;		// number of bits of indexes
    int n_indexes;		// number of indexes
    int nbits_block_size;	// number of bits of block size
    int block_size;		// size of one block (in bytes)

    // performance statistics
    int n_accesses;
    int n_misses;
    int n_replacements;

    // T.B.D.
    // put here your necessary member variables for manipulating cache


    //
    // helper functions
    //
    int address_to_tag(sim_addr addr) {
	return addr >> (nbits_block_size + nbits_indexes);
    }

    int address_to_index(sim_addr addr) {
	return (addr >> nbits_block_size) & (n_indexes - 1);
    }

    int address_to_block_offset(sim_addr addr) {
	return addr & (block_size - 1);
    }

    bool has_data(sim_addr addr);		// is data cached ?

    int victim_way(sim_addr addr);              // get victim way
    bool victim_needs_copyback(sim_addr addr, int way); // victim block needs copyback ?

    // allocate/fill block for 'addr'
    void fill_block(sim_addr addr, int way);

    // access block (block is touched)
    void access_block(sim_addr addr, bool write_flag);


    // T.B.D.
    // put here your original (private) member functions for manipulating cache
    // (if necessary)

public:
    // constructor
    // (default: 2-way set associative, 32 entries per way, 8 bytes per block)
    cache(int n_ways = 2, int nbits_indexes = 5, int nbits_block_size = 3);

    // destructor
    ~cache();

    // connect memory
    void connect_memory(memory *mem);

    // check cache's readiness
    bool is_ready();				// ready to request
    bool is_done();				// ready to get reply

    // request data read
    void request_read_byte(sim_addr addr);
    void request_read_hword(sim_addr addr);
    void request_read_word(sim_addr addr);
    void request_read_dword(sim_addr addr);

    // reply data read
    void reply_read_byte(sim_addr addr, sim_byte &val);
    void reply_read_hword(sim_addr addr, sim_hword &val);
    void reply_read_word(sim_addr addr, sim_word &val);
    void reply_read_dword(sim_addr addr, sim_dword &val);

    // request data write
    void request_write_byte(sim_addr addr, sim_byte val);
    void request_write_hword(sim_addr addr, sim_hword val);
    void request_write_word(sim_addr addr, sim_word val);
    void request_write_dword(sim_addr addr, sim_dword val);

    // reply data write
    void reply_write_byte(sim_addr addr);
    void reply_write_hword(sim_addr addr);
    void reply_write_word(sim_addr addr);
    void reply_write_dword(sim_addr addr);

    // cancel request
    void cancel_request();

    // update cache's internal state (to be called in each cycle)
    void update();

    // print statistics information
    void print_statistics();

    // misc.
    void print_set(int index_addr);
    void print_all();
};

#endif
// end of cache.h