// -*- c++ -*-
//
// memory.cc:
// (little-endian access only)
//
#include <iostream>
#include <cstdlib>
#include "memory.h"

using namespace std;

//
// constructor
//
memory::memory()
{
    for (int i = 0; i < root_table_size; i++)
	root_table[i] = NULL;
}

//
// destructor
//
memory::~memory()
{
    for (int i = 0; i < root_table_size; i++) {
	if (root_table[i] != NULL) {
	    mblock_table tab = root_table[i];
	    for (int j = 0; j < mblock_table_size; j++)
		if (tab[j] != NULL)
		    delete [] tab[j];
	    delete [] root_table[i];
	}
    }
}

//
// memory class member functions
//

void
memory::check_address(sim_addr addr, int size)
{
//    cerr << "memory::check_address: " << hex << addr << endl;

    // valid address ranges from 0x00000000 to 0x7fffffff
//    if (addr + size >= 0x80000000) {
//	cerr << "invalid address " << hex << addr << endl;
//	exit(1);
//    }

    if (!is_allocated(addr, size)) {
	cerr << "memory address " << hex << addr
	     << " not allocated" << endl;
	exit(1);
    }
    if ((addr & (size - 1))) {
	cerr << "alignment error " << hex << addr << endl;
	exit(1);
    }
}

sim_word
memory::get(sim_addr addr)
{
    int tab_no = mblock_table_number(addr);
    mblock_table tab = root_table[tab_no];
    int idx = mblock_index(addr);
    int offset = mblock_offset(addr);
    sim_word val = tab[idx][offset/sizeof(sim_word)];

//    cerr << "memory::get(addr=" << hex << addr << ")"
//	 << " => return " << hex << val << endl;;

    return val;
}

void
memory::put(sim_addr addr, sim_word val)
{
    int tab_no = mblock_table_number(addr);
    mblock_table tab = root_table[tab_no];
    int idx = mblock_index(addr);
    int offset = mblock_offset(addr);
    tab[idx][offset/sizeof(sim_word)] = val;

//    cerr << "memory::put(addr=" << hex << addr
//	 << ", val=" << hex << val << ")" << endl;
}

void
memory::alloc(sim_addr addr, sim_size sz)
{
//    cerr << "memory::alloc(" << hex << addr << ", " << sz << ")" << endl;

    int tab_no = mblock_table_number(addr);

    // NOTE:
    // allocation cross memory block table boundary not supported
    // (fix me later)

    if (root_table[tab_no] == NULL) {
	root_table[tab_no] = new mblock[mblock_table_size];
	for (int i = 0; i < mblock_table_size; i++)	// null clear
	    root_table[tab_no][i] = NULL;
//	cerr << "allocate mblock_table (no." << tab_no << ")" << endl;
    }

    mblock_table tab = root_table[tab_no];
    sim_addr base = mblock_base(addr);
    sim_addr base_end = mblock_base(addr + sz);

    while (base <= base_end) {
	int idx = mblock_index(base);
	if (tab[idx] == NULL) {
	    tab[idx] = new sim_word[mblock_size];
	    for (int i = 0; i < mblock_size; i++)	// zero clear
		tab[idx][i] = 0;
//	    cerr << "allocate mblock (index " << tab_no << ")" << endl;
	}
	base += mblock_size;
    }
}

void
memory::free(sim_addr addr)
{
    // (CURRENTLY NO ONE CALLS THIS MEMBER FUNCTION)

    // NOTE:
    // If you want to implement this member function,
    // you must check the free-ness of the specified memory block before actually free-ing it.
    // (but it is not so easy to completely check it.)
    //
    cerr << "memory::free() is not implemented yet" << endl;
}

void
memory::clear(sim_addr addr, sim_size sz, sim_byte val)
{
    for ( ; sz > 0; sz--)
	write_byte(addr, val);
}

bool
memory::is_allocated(sim_addr addr)
{
//    cerr << "memory::is_allocated(" << hex << addr << ")" << endl;

    int tab_no = mblock_table_number(addr);
    if (tab_no >= root_table_size)
	return false;

    mblock_table tab = root_table[tab_no];
    if (tab == NULL)
	return false;

    if (tab[mblock_index(addr)] == NULL)
	return false;

    return true;
}

bool
memory::is_allocated(sim_addr addr, sim_size sz)
{
//    cerr << "memory::is_allocated("
//	 << hex << addr << ", " << sz << ")" << endl;

    if (!is_allocated(addr))
	return false;

    for ( ; sz > 0; sz -= mblock_size) {
	if (!is_allocated(addr + sz))
	    return false;

	addr += mblock_size;
    }

    return true;
}

sim_byte
memory::read_byte(sim_addr addr)
{
    check_address(addr, sizeof(sim_byte));
    sim_word tmp = get(addr);
    sim_byte v = (tmp >> ((addr & 3)*8)) & 0xff;
    return v;
}

sim_hword
memory::read_hword(sim_addr addr)
{
    check_address(addr, sizeof(sim_hword));
    sim_word tmp = get(addr);
    sim_hword v = (tmp >> ((addr & 2)*8)) & 0xffff;
    return v;
}

sim_word
memory::read_word(sim_addr addr)
{
    check_address(addr, sizeof(sim_word));
    return get(addr);
}

sim_dword
memory::read_dword(sim_addr addr)
{
    check_address(addr, sizeof(sim_dword));
    sim_dword v;
    v.h = get(addr + 4);
    v.l = get(addr);
    return v;
}

void
memory::write_byte(sim_addr addr, sim_byte val)
{
    check_address(addr, sizeof(sim_byte));
    sim_word tmp = get(addr);
    put(addr, (tmp & ~(0xff << (addr & 3)*8))|(val << (addr & 3)*8));
}

void
memory::write_hword(sim_addr addr, sim_hword val)
{
    check_address(addr, sizeof(sim_hword));
    sim_word tmp = get(addr);
    put(addr, (tmp & ~(0xffff << (addr & 2)*8))|(val << (addr & 2)*8));
}

void
memory::write_word(sim_addr addr, sim_word val)
{
    check_address(addr, sizeof(sim_word));
    put(addr, val);
}

void
memory::write_dword(sim_addr addr, sim_dword val)
{
    check_address(addr, sizeof(sim_dword));
    put(addr + 4, val.h);
    put(addr, val.l);
}

void
memory::print(sim_addr addr, sim_size sz)
{
    cout << "memory dump " << hex << addr << "-" << (addr+sz) << endl;
    for ( ; sz > 0; sz--) {
	if (is_allocated(addr)) {
	    cout << hex << addr << ": "
		 << (int)read_byte(addr) << endl;
	    addr++;
	}
    }
}

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

    mem.alloc(0, 16);
    mem.clear(0, 16);

    for (int i = 0; i < 16; i+=sizeof(sim_hword))
	mem.write_hword(i, i+1);

    for (int i = 0; i < 16; i+=sizeof(sim_hword)) {
	cout << i << ": " << 
	    (int)mem.read_hword(i) << endl;
	}
    return 0;
}
#endif

// end of memory.cc