/*
 * Copyright (c) 2004-2005 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Lisa Hsu
 */

/**
 * @file
 * Definitions of split cache tag store.
 */

#include <string>
#include <iostream>
#include <fstream>

#include "base/cprintf.hh"
#include "base/intmath.hh"
#include "base/output.hh"
#include "base/trace.hh"
#include "mem/cache/base_cache.hh"
#include "mem/cache/tags/split.hh"
#include "mem/cache/tags/split_lifo.hh"
#include "mem/cache/tags/split_lru.hh"


using namespace std;
using namespace TheISA;

// create and initialize a partitioned cache structure
Split::Split(int _numSets, int _blkSize, int total_ways, int LRU1_assoc,
             bool _lifo, bool _two_queue, int _hit_latency) :
    numSets(_numSets), blkSize(_blkSize), lifo(_lifo), hitLatency(_hit_latency)
{
    DPRINTF(Split, "new split cache!!\n");

    DPRINTF(Split, "lru has %d numSets, %d blkSize, %d assoc, and %d hit_latency\n",
            numSets, blkSize, LRU1_assoc, hitLatency);

    lru = new SplitLRU(_numSets, _blkSize, LRU1_assoc, _hit_latency, 1);

    if (total_ways - LRU1_assoc == 0) {
        lifo_net = NULL;
        lru_net = NULL;
    } else {
        if (lifo) {
            DPRINTF(Split, "Other partition is a LIFO with size %d in bytes. it gets %d ways\n",
                    (total_ways - LRU1_assoc)*_numSets*_blkSize, (total_ways - LRU1_assoc));
            lifo_net = new SplitLIFO(_blkSize, (total_ways - LRU1_assoc)*_numSets*_blkSize,
                                     (total_ways - LRU1_assoc), _hit_latency, _two_queue, 2);
            lru_net = NULL;
        }
        else {
            DPRINTF(Split, "other LRU gets %d ways\n", total_ways - LRU1_assoc);
            lru_net = new SplitLRU(_numSets, _blkSize, total_ways - LRU1_assoc, _hit_latency, 2);
            lifo_net = NULL;
        }
    }

    blkMask = blkSize - 1;

    if (!isPowerOf2(total_ways))
        warn("total cache ways/columns %d should be power of 2",
             total_ways);

    warmedUp = false;
    /** @todo Make warmup percentage a parameter. */
    warmupBound = numSets * total_ways;

}

Split::~Split()
{
    delete lru;
    if (lifo)
        delete lifo_net;
    else
        delete lru_net;
}

void
Split::regStats(const string &name)
{
    using namespace Stats;

    BaseTags::regStats(name);

    usedEvictDist.init(0,3000,40);
    unusedEvictDist.init(0,3000,40);
    useByCPUCycleDist.init(0,35,1);

    nic_repl
        .name(name + ".nic_repl")
        .desc("number of replacements in the nic partition")
        .precision(0)
        ;

    cpu_repl
        .name(name + ".cpu_repl")
        .desc("number of replacements in the cpu partition")
        .precision(0)
        ;

    lru->regStats(name + ".lru");

    if (lifo && lifo_net) {
        lifo_net->regStats(name + ".lifo_net");
    } else if (lru_net) {
        lru_net->regStats(name + ".lru_net");
    }

    nicUsedWhenEvicted
        .name(name + ".nicUsedWhenEvicted")
        .desc("number of NIC blks that were used before evicted")
        ;

    nicUsedTotLatency
        .name(name + ".nicUsedTotLatency")
        .desc("total cycles before eviction of used NIC blks")
        ;

    nicUsedTotEvicted
        .name(name + ".nicUsedTotEvicted")
        .desc("total number of used NIC blks evicted")
        ;

    nicUsedAvgLatency
        .name(name + ".nicUsedAvgLatency")
        .desc("avg number of cycles a used NIC blk is in cache")
        .precision(0)
        ;
    nicUsedAvgLatency = nicUsedTotLatency / nicUsedTotEvicted;

    usedEvictDist
        .name(name + ".usedEvictDist")
        .desc("distribution of used NIC blk eviction times")
        .flags(pdf | cdf)
        ;

    nicUnusedWhenEvicted
        .name(name + ".nicUnusedWhenEvicted")
        .desc("number of NIC blks that were unused when evicted")
        ;

    nicUnusedTotLatency
        .name(name + ".nicUnusedTotLatency")
        .desc("total cycles before eviction of unused NIC blks")
        ;

    nicUnusedTotEvicted
        .name(name + ".nicUnusedTotEvicted")
        .desc("total number of unused NIC blks evicted")
        ;

    nicUnusedAvgLatency
        .name(name + ".nicUnusedAvgLatency")
        .desc("avg number of cycles an unused NIC blk is in cache")
        .precision(0)
        ;
    nicUnusedAvgLatency = nicUnusedTotLatency / nicUnusedTotEvicted;

    unusedEvictDist
        .name(name + ".unusedEvictDist")
        .desc("distribution of unused NIC blk eviction times")
        .flags(pdf | cdf)
        ;

    nicUseByCPUCycleTotal
        .name(name + ".nicUseByCPUCycleTotal")
        .desc("total latency of NIC blks til usage time")
        ;

    nicBlksUsedByCPU
        .name(name + ".nicBlksUsedByCPU")
        .desc("total number of NIC blks used")
        ;

    nicAvgUsageByCPULatency
        .name(name + ".nicAvgUsageByCPULatency")
        .desc("average number of cycles before a NIC blk that is used gets used")
        .precision(0)
        ;
    nicAvgUsageByCPULatency = nicUseByCPUCycleTotal / nicBlksUsedByCPU;

    useByCPUCycleDist
        .name(name + ".useByCPUCycleDist")
        .desc("the distribution of cycle time in cache before NIC blk is used")
        .flags(pdf | cdf)
        ;

    cpuUsedBlks
        .name(name + ".cpuUsedBlks")
        .desc("number of cpu blks that were used before evicted")
        ;

    cpuUnusedBlks
        .name(name + ".cpuUnusedBlks")
        .desc("number of cpu blks that were unused before evicted")
        ;

    nicAvgLatency
        .name(name + ".nicAvgLatency")
        .desc("avg number of cycles a NIC blk is in cache before evicted")
        .precision(0)
        ;
    nicAvgLatency = (nicUnusedTotLatency + nicUsedTotLatency) /
        (nicUnusedTotEvicted + nicUsedTotEvicted);

    NR_CP_hits
        .name(name + ".NR_CP_hits")
        .desc("NIC requests hitting in CPU Partition")
        ;

    NR_NP_hits
        .name(name + ".NR_NP_hits")
        .desc("NIC requests hitting in NIC Partition")
        ;

    CR_CP_hits
        .name(name + ".CR_CP_hits")
        .desc("CPU requests hitting in CPU partition")
        ;

    CR_NP_hits
        .name(name + ".CR_NP_hits")
        .desc("CPU requests hitting in NIC partition")
        ;

}

// probe cache for presence of given block.
bool
Split::probe(int asid, Addr addr) const
{
    bool success = lru->probe(asid, addr);
    if (!success) {
        if (lifo && lifo_net)
            success = lifo_net->probe(asid, addr);
        else if (lru_net)
            success = lru_net->probe(asid, addr);
    }

    return success;
}

SplitBlk*
Split::findBlock(Packet * &pkt, int &lat)
{

    Addr aligned = blkAlign(pkt->getAddr());

    if (memHash.count(aligned)) {
        memHash[aligned]++;
    } else if (pkt->nic_pkt()) {
        memHash[aligned] = 1;
    }

    SplitBlk *blk = lru->findBlock(pkt->getAddr(), pkt->req->getAsid(), lat);
    if (blk) {
        if (pkt->nic_pkt()) {
            NR_CP_hits++;
        } else {
            CR_CP_hits++;
        }
    } else {
        if (lifo && lifo_net) {
            blk = lifo_net->findBlock(pkt->getAddr(), pkt->req->getAsid(), lat);

        } else if (lru_net) {
            blk = lru_net->findBlock(pkt->getAddr(), pkt->req->getAsid(), lat);
        }
        if (blk) {
            if (pkt->nic_pkt()) {
                NR_NP_hits++;
            } else {
                CR_NP_hits++;
            }
        }
    }

    if (blk) {
        Tick latency = curTick - blk->ts;
        if (blk->isNIC) {
            if (!blk->isUsed && !pkt->nic_pkt()) {
                    useByCPUCycleDist.sample(latency);
                    nicUseByCPUCycleTotal += latency;
                    nicBlksUsedByCPU++;
            }
        }
        blk->isUsed = true;

        if (pkt->nic_pkt()) {
            DPRINTF(Split, "found block in partition %d\n", blk->part);
        }
    }
    return blk;
}

SplitBlk*
Split::findBlock(Addr addr, int asid, int &lat)
{
    SplitBlk *blk = lru->findBlock(addr, asid, lat);
    if (!blk) {
        if (lifo && lifo_net) {
            blk = lifo_net->findBlock(addr, asid, lat);
        } else if (lru_net) {
            blk = lru_net->findBlock(addr, asid, lat);
        }
    }

    return blk;
}

SplitBlk*
Split::findBlock(Addr addr, int asid) const
{
    SplitBlk *blk = lru->findBlock(addr, asid);
    if (!blk) {
        if (lifo && lifo_net) {
            blk = lifo_net->findBlock(addr, asid);
        } else if (lru_net) {
            blk = lru_net->findBlock(addr, asid);
        }
    }

    return blk;
}

SplitBlk*
Split::findReplacement(Packet * &pkt, PacketList &writebacks,
                     BlkList &compress_blocks)
{
    SplitBlk *blk;

    if (pkt->nic_pkt()) {
        DPRINTF(Split, "finding a replacement for nic_req\n");
        nic_repl++;
        if (lifo && lifo_net)
            blk = lifo_net->findReplacement(pkt, writebacks,
                                             compress_blocks);
        else if (lru_net)
            blk = lru_net->findReplacement(pkt, writebacks,
                                            compress_blocks);
        // in this case, this is an LRU only cache, it's non partitioned
        else
            blk = lru->findReplacement(pkt, writebacks, compress_blocks);
    } else {
        DPRINTF(Split, "finding replacement for cpu_req\n");
        blk = lru->findReplacement(pkt, writebacks,
                                    compress_blocks);
        cpu_repl++;
    }

    Tick latency = curTick - blk->ts;
    if (blk->isNIC) {
        if (blk->isUsed) {
            nicUsedWhenEvicted++;
            usedEvictDist.sample(latency);
            nicUsedTotLatency += latency;
            nicUsedTotEvicted++;
        } else {
            nicUnusedWhenEvicted++;
            unusedEvictDist.sample(latency);
            nicUnusedTotLatency += latency;
            nicUnusedTotEvicted++;
        }
    } else {
        if (blk->isUsed) {
            cpuUsedBlks++;
        } else {
            cpuUnusedBlks++;
        }
    }

    // blk attributes for the new blk coming IN
    blk->ts = curTick;
    blk->isNIC = (pkt->nic_pkt()) ? true : false;

    return blk;
}

void
Split::invalidateBlk(int asid, Addr addr)
{
    SplitBlk *blk = lru->findBlock(addr, asid);
    if (!blk) {
        if (lifo && lifo_net)
            blk = lifo_net->findBlock(addr, asid);
        else if (lru_net)
            blk = lru_net->findBlock(addr, asid);

        if (!blk)
            return;
    }

    blk->status = 0;
    blk->isTouched = false;
    tagsInUse--;
}

void
Split::doCopy(Addr source, Addr dest, int asid, PacketList &writebacks)
{
    if (lru->probe(asid, source))
        lru->doCopy(source, dest, asid, writebacks);
    else {
        if (lifo && lifo_net)
            lifo_net->doCopy(source, dest, asid, writebacks);
        else if (lru_net)
            lru_net->doCopy(source, dest, asid, writebacks);
    }
}

void
Split::cleanupRefs()
{
    lru->cleanupRefs();
    if (lifo && lifo_net)
        lifo_net->cleanupRefs();
    else if (lru_net)
        lru_net->cleanupRefs();

    ofstream memPrint(simout.resolve("memory_footprint.txt").c_str(),
                      ios::trunc);

    // this shouldn't be here but it happens at the end, which is what i want
    memIter end = memHash.end();
    for (memIter iter = memHash.begin(); iter != end; ++iter) {
        ccprintf(memPrint, "%8x\t%d\n", (*iter).first, (*iter).second);
    }
}

Addr
Split::regenerateBlkAddr(Addr tag, int set) const
{
    if (lifo_net)
        return lifo_net->regenerateBlkAddr(tag, set);
    else
        return lru->regenerateBlkAddr(tag, set);
}

Addr
Split::extractTag(Addr addr, SplitBlk *blk) const
{
    if (blk->part == 2) {
        if (lifo_net)
            return lifo_net->extractTag(addr);
        else if (lru_net)
            return lru_net->extractTag(addr);
        else
            panic("this shouldn't happen");
    } else
        return lru->extractTag(addr);
}

