// -*- c++ -*-
//
// cache.h:
// base type cache (perfect hit 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;	// delay for memory read access
    static const int memory_write_delay = 10;	// delay for memory write access

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

    //
    // type definitions
    //
    enum command_type {
	command_null = 0,
	command_read_request,
	command_write_request,
	command_read_reply,
	command_write_reply,
    };

    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 allowed)
    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(); }
    };

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

    port request_port;		// port for cache request
    port reply_port;		// port for cache reply

    int busy_count;		// busy counter for current request processing

    // parameters
    int num_of_ways;		// cache associativity
    int nbits_of_sets;		// number of bits for sets
    int num_of_sets;		// number of sets
    int nbits_of_block_size;	// number of bits for block size
    int block_size;		// size of one block (in bytes)

    //
    // helper functions
    //
    void release_request_port();		// request process done
    void release_reply_port();			// reply process done

    bool has_data(sim_addr addr);		// check whether data is cached
    bool has_copyback_data(sim_addr addr); 	// check whether replacement is required

    void alloc(sim_addr addr);			// allocate new data entry onto cache
    void alloc_with_replacement(sim_addr addr);	// allocate new entry onto cache
                                                // with block replacement

    int address_to_tag(sim_addr addr) {
	return addr >> (nbits_of_block_size + nbits_of_sets);
    }

    int address_to_index(sim_addr addr) {
	return (addr >> nbits_of_block_size) & (num_of_sets - 1);
    }
    
    int address_to_block_offset(sim_addr addr) {
	return addr & (block_size - 1);
    }

public:
    // constructor
    // (default: 2-way set associative, 256 entries per set, 16 bytes per block)
    cache(int nways = 2, int nbits_sets = 8, int nbits_block_size = 4);

    // 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);

    // update state (called for each cycle)
    void update();

    // misc.
    void print(sim_addr addr);
    void print_all();
};

#endif
// end of cache.h