/**
 * @file
 * @copyright  Copyright 2016 GNSS Sensor Ltd. All right reserved.
 * @author     Sergey Khabarov - sergeykhbr@gmail.com
 * @brief      Data Cache.
 */

#include "dcache.h"

namespace debugger {

DCache::DCache(sc_module_name name_) : sc_module(name_) {
    SC_METHOD(comb);
    sensitive << i_nrst;
    sensitive << i_req_data_valid;
    sensitive << i_req_data_write;
    sensitive << i_req_data_sz;
    sensitive << i_req_data_addr;
    sensitive << i_req_data_data;
    sensitive << i_resp_mem_data_valid;
    sensitive << i_resp_mem_data;
    sensitive << i_req_mem_ready;
    sensitive << i_resp_data_ready;
    sensitive << r.dline_data;
    sensitive << r.dline_addr_req;
    sensitive << r.dline_size_req;
    sensitive << r.state;

    SC_METHOD(registers);
    sensitive << i_clk.pos();
};

void DCache::generateVCD(sc_trace_file *i_vcd, sc_trace_file *o_vcd) {
    if (o_vcd) {
        sc_trace(o_vcd, i_req_data_valid, "/top/cache0/d0/i_req_data_valid");
        sc_trace(o_vcd, i_req_data_write, "/top/cache0/d0/i_req_data_write");
        sc_trace(o_vcd, i_req_data_sz, "/top/cache0/d0/i_req_data_sz");
        sc_trace(o_vcd, i_req_data_addr, "/top/cache0/d0/i_req_data_addr");
        sc_trace(o_vcd, i_req_data_data, "/top/cache0/d0/i_req_data_data");
        sc_trace(o_vcd, o_req_mem_addr, "/top/cache0/d0/o_req_mem_addr");
        sc_trace(o_vcd, o_req_mem_strob, "/top/cache0/d0/o_req_mem_strob");
        sc_trace(o_vcd, o_req_mem_data, "/top/cache0/d0/o_req_mem_data");
        sc_trace(o_vcd, o_resp_data_valid, "/top/cache0/d0/o_resp_data_valid");
        sc_trace(o_vcd, o_resp_data_addr, "/top/cache0/d0/o_resp_data_addr");
        sc_trace(o_vcd, o_resp_data_data, "/top/cache0/d0/o_resp_data_data");
        sc_trace(o_vcd, r.dline_data, "/top/cache0/d0/r_dline_data");
        sc_trace(o_vcd, r.dline_addr_req, "/top/cache0/d0/r_dline_addr_req");
        sc_trace(o_vcd, r.dline_size_req, "/top/cache0/d0/r_dline_size_req");
        sc_trace(o_vcd, r.state, "/top/cache0/d0/r_state");
    }
}

void DCache::comb() {
    bool w_o_req_data_ready;
    bool w_o_req_mem_valid;
    sc_uint<BUS_ADDR_WIDTH> wb_o_req_mem_addr;
    sc_uint<BUS_DATA_BYTES> wb_o_req_strob;
    sc_uint<BUS_DATA_WIDTH> wb_o_req_wdata;
    bool w_req_fire;
    bool w_o_resp_valid;
    sc_uint<BUS_ADDR_WIDTH> wb_o_resp_addr;
    sc_uint<BUS_DATA_WIDTH> wb_resp_data_mux;
    sc_uint<BUS_DATA_WIDTH> wb_o_resp_data;
    sc_uint<BUS_DATA_WIDTH> wb_rtmp;

    v = r;

    wb_o_req_strob = 0;
    wb_o_req_wdata = 0;
    wb_o_resp_data = 0;
    wb_rtmp = 0;

    switch (i_req_data_sz.read()) {
    case 0:
        wb_o_req_wdata = (i_req_data_data.read()(7, 0),
            i_req_data_data.read()(7, 0), i_req_data_data.read()(7, 0),
            i_req_data_data.read()(7, 0), i_req_data_data.read()(7, 0),
            i_req_data_data.read()(7, 0), i_req_data_data.read()(7, 0),
            i_req_data_data.read()(7, 0));
        if (i_req_data_addr.read()(2, 0) == 0x0) {
            wb_o_req_strob = 0x01;
        } else if (i_req_data_addr.read()(2, 0) == 0x1) {
            wb_o_req_strob = 0x02;
        } else if (i_req_data_addr.read()(2, 0) == 0x2) {
            wb_o_req_strob = 0x04;
        } else if (i_req_data_addr.read()(2, 0) == 0x3) {
            wb_o_req_strob = 0x08;
        } else if (i_req_data_addr.read()(2, 0) == 0x4) {
            wb_o_req_strob = 0x10;
        } else if (i_req_data_addr.read()(2, 0) == 0x5) {
            wb_o_req_strob = 0x20;
        } else if (i_req_data_addr.read()(2, 0) == 0x6) {
            wb_o_req_strob = 0x40;
        } else if (i_req_data_addr.read()(2, 0) == 0x7) {
            wb_o_req_strob = 0x80;
        }
        break;
    case 1:
        wb_o_req_wdata = (i_req_data_data.read()(15, 0),
            i_req_data_data.read()(15, 0), i_req_data_data.read()(15, 0),
            i_req_data_data.read()(15, 0));
        if (i_req_data_addr.read()(2, 1) == 0) {
            wb_o_req_strob = 0x03;
        } else if (i_req_data_addr.read()(2, 1) == 1) {
            wb_o_req_strob = 0x0C;
        } else if (i_req_data_addr.read()(2, 1) == 2) {
            wb_o_req_strob = 0x30;
        } else {
            wb_o_req_strob = 0xC0;
        }
        break;
    case 2:
        wb_o_req_wdata = (i_req_data_data.read()(31, 0),
                    i_req_data_data.read()(31, 0));
        if (i_req_data_addr.read()[2]) {
            wb_o_req_strob = 0xF0;
        } else {
            wb_o_req_strob = 0x0F;
        }
        break;
    case 3:
        wb_o_req_wdata = i_req_data_data;
        wb_o_req_strob = 0xFF;
        break;
    default:;
    }

    w_o_req_mem_valid = i_req_data_valid.read();
    wb_o_req_mem_addr = i_req_data_addr.read()(BUS_ADDR_WIDTH-1, 3) << 3;
    w_o_req_data_ready = i_req_mem_ready.read();
    w_req_fire = i_req_data_valid.read() && w_o_req_data_ready;
    switch (r.state.read()) {
    case State_Idle:
        if (i_req_data_valid.read()) {
            if (i_req_mem_ready.read()) {
                v.state = State_WaitResp;
            } else {
                v.state = State_WaitGrant;
            }
        }
        break;
    case State_WaitGrant:
        if (i_req_mem_ready.read()) {
            v.state = State_WaitResp;
        }
        break;
    case State_WaitResp:
        if (i_resp_mem_data_valid.read()) {
            if (!i_resp_data_ready.read()) {
                v.state = State_WaitAccept;
            } else if (!i_req_data_valid.read()) {
                v.state = State_Idle;
            } else {
                // New request
                if (i_req_mem_ready.read()) {
                    v.state = State_WaitResp;
                } else {
                    v.state = State_WaitGrant;
                }
            }
        }
        break;
    case State_WaitAccept:
        if (i_resp_data_ready.read()) {
            if (!i_req_data_valid.read()) {
                v.state = State_Idle;
            } else {
                if (i_req_mem_ready.read()) {
                    v.state = State_WaitResp;
                } else {
                    v.state = State_WaitGrant;
                }
            }
        }
        break;
    default:;
    }

    if (w_req_fire) {
        v.dline_addr_req = i_req_data_addr;
        v.dline_size_req = i_req_data_sz;
    }
    if (i_resp_mem_data_valid.read()) {
        v.dline_data =  i_resp_mem_data;
    }

    wb_o_resp_addr = r.dline_addr_req;
    if (r.state.read() == State_WaitAccept) {
        w_o_resp_valid = 1;
        wb_resp_data_mux = r.dline_data;
    } else {
        w_o_resp_valid = i_resp_mem_data_valid;
        wb_resp_data_mux = i_resp_mem_data;
    }

    switch (r.dline_addr_req.read()(2, 0)) {
    case 1:
        wb_rtmp = wb_resp_data_mux(63, 8);
        break;
    case 2:
        wb_rtmp = wb_resp_data_mux(63, 16);
        break;
    case 3:
        wb_rtmp = wb_resp_data_mux(63, 24);
        break;
    case 4:
        wb_rtmp = wb_resp_data_mux(63, 32);
        break;
    case 5:
        wb_rtmp = wb_resp_data_mux(63, 40);
        break;
    case 6:
        wb_rtmp = wb_resp_data_mux(63, 48);
        break;
    case 7:
        wb_rtmp = wb_resp_data_mux(63, 56);
        break;
    default:
        wb_rtmp = wb_resp_data_mux;
    } 

    switch (r.dline_size_req.read()) {
    case 0:
        wb_o_resp_data = wb_rtmp(7, 0);
        break;
    case 1:
        wb_o_resp_data = wb_rtmp(15, 0);
        break;
    case 2:
        wb_o_resp_data = wb_rtmp(31, 0);
        break;
    default:
        wb_o_resp_data = wb_rtmp;
    }
    
    if (!i_nrst.read()) {
        v.dline_addr_req = 0;
        v.dline_size_req = 0;
        v.dline_data = 0;
        v.state = State_Idle;
    }

    o_req_data_ready = w_o_req_data_ready;

    o_req_mem_valid = w_o_req_mem_valid;
    o_req_mem_addr = wb_o_req_mem_addr;
    o_req_mem_write = i_req_data_write;
    o_req_mem_strob = wb_o_req_strob;
    o_req_mem_data = wb_o_req_wdata;

    o_resp_data_valid = w_o_resp_valid;
    o_resp_data_data = wb_o_resp_data;
    o_resp_data_addr = wb_o_resp_addr;
    o_dstate = r.state;
}

void DCache::registers() {
    r = v;
}

}  // namespace debugger

