/*
 * Copyright (c) 2002-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: Erik Hallnor
 */

/**
 * @file
 * Declaration of the Indirect Index Cache (IIC) tags store.
 */

#ifndef __IIC_HH__
#define __IIC_HH__

#include <list>
#include <vector>

#include "mem/cache/cache_blk.hh"
#include "mem/cache/tags/repl/repl.hh"
#include "mem/packet.hh"
#include "base/statistics.hh"
#include "mem/cache/tags/base_tags.hh"

class BaseCache; // Forward declaration

/**
 * IIC cache blk.
 */
class IICTag : public CacheBlk
{
  public:
    /**
     * Copy the contents of the given IICTag into this one.
     * @param rhs The tag to copy.
     * @return const reference to this tag.
     */
    const IICTag& operator=(const IICTag& rhs)
    {
        CacheBlk::operator=(rhs);
        chain_ptr = rhs.chain_ptr;
        re = rhs.re;
        set = rhs.set;
        trivialData = rhs.trivialData;
        numData = rhs.numData;
        data_ptr.clear();
        for (int i = 0; i < rhs.numData; ++i) {
            data_ptr.push_back(rhs.data_ptr[i]);
        }
        return *this;
    }

    /** Hash chain pointer into secondary store. */
    unsigned long chain_ptr;
    /** Data array pointers for each subblock. */
    std::vector<unsigned long> data_ptr;
    /** Replacement Entry pointer. */
    void *re;
    /**
     * An array to store small compressed data. Conceputally the same size
     * as the unsused data array pointers.
     */
    uint8_t *trivialData;
    /**
     * The number of allocated subblocks.
     */
    int numData;
};

/**
 * A hash set for the IIC primary lookup table.
 */
class IICSet{
  public:
    /** The associativity of the primary table. */
    int assoc;

    /** The number of hash chains followed when finding the last block. */
    int depth;
    /** The current number of blocks on the chain. */
    int size;

    /** Tag pointer into the secondary tag storage. */
    unsigned long chain_ptr;

    /** The LRU list of the primary table. MRU is at 0 index. */
    IICTag ** tags;

    /**
     * Find the addr in this set, return the chain pointer to the secondary if
     * it isn't found.
     * @param asid The address space ID.
     * @param tag The address to find.
     * @param chain_ptr The chain pointer to start the search of the secondary
     * @return Pointer to the tag, NULL if not found.
     */
    IICTag* findTag(int asid, Addr tag, unsigned long &chain_ptr)
    {
        depth = 1;
        for (int i = 0; i < assoc; ++i) {
            if (tags[i]->tag == tag && tags[i]->isValid()) {
                return tags[i];
            }
        }
        chain_ptr = this->chain_ptr;
        return 0;
    }

    /**
     * Find an usused tag in this set.
     * @return Pointer to the unused tag, NULL if none are free.
     */
    IICTag* findFree()
    {
        for (int i = 0; i < assoc; ++i) {
            if (!tags[i]->isValid()) {
                return tags[i];
            }
        }
        return 0;
    }

    /**
     * Move a tag to the head of the LRU list
     * @param tag The tag to move.
     */
    void moveToHead(IICTag *tag);

    /**
     * Move a tag to the tail (LRU) of the LRU list
     * @param tag The tag to move.
     */
    void moveToTail(IICTag *tag);
};

/**
 * The IIC tag store. This is a hardware-realizable, fully-associative tag
 * store that uses software replacement, e.g. Gen.
 */
class IIC : public BaseTags
{
  public:
    /** Typedef of the block type used in this class. */
    typedef IICTag BlkType;
    /** Typedef for list of pointers to the local block type. */
    typedef std::list<IICTag*> BlkList;
  protected:
    /** The number of set in the primary table. */
    const int hashSets;
    /** The block size in bytes. */
    const int blkSize;
    /** The associativity of the primary table. */
    const int assoc;
    /** The base hit latency. */
    const int hitLatency;
    /** The subblock size, used for compression. */
    const int subSize;

    /** The number of subblocks */
    const int numSub;
    /** The number of bytes used by data pointers */
    const int trivialSize;

    /** The amount to shift address to get the tag. */
    const int tagShift;
    /** The mask to get block offset bits. */
    const unsigned blkMask;

    /** The amount to shift to get the subblock number. */
    const int subShift;
    /** The mask to get the correct subblock number. */
    const unsigned subMask;

    /** The latency of a hash lookup. */
    const int hashDelay;
    /** The number of data blocks. */
    const int numBlocks;
    /** The total number of tags in primary and secondary. */
    const int numTags;
    /** The number of tags in the secondary tag store. */
    const int numSecondary;

    /** The Null tag pointer. */
    const int tagNull;
    /** The last tag in the primary table. */
    const int primaryBound;

    /** All of the tags */
    IICTag *tagStore;
    /**
     * Pointer to the head of the secondary freelist (maintained with chain
     * pointers.
     */
    unsigned long freelist;
    /**
     * The data block freelist.
     */
    std::list<unsigned long> blkFreelist;

    /** The primary table. */
    IICSet *sets;

    /** The replacement policy. */
    Repl *repl;

    /** An array of data reference counters. */
    int *dataReferenceCount;

    /** The data blocks. */
    uint8_t *dataStore;

    /** Storage for the fast access data of each cache block. */
    uint8_t **dataBlks;

    /**
     * Count of the current number of free secondary tags.
     * Used for debugging.
     */
    int freeSecond;

    // IIC Statistics
    /**
     * @addtogroup IICStatistics IIC Statistics
     * @{
     */

    /** Hash hit depth of cache hits. */
    Stats::Distribution<> hitHashDepth;
    /** Hash depth for cache misses. */
    Stats::Distribution<> missHashDepth;
    /** Count of accesses to each hash set. */
    Stats::Distribution<> setAccess;

    /** The total hash depth for every miss. */
    Stats::Scalar<> missDepthTotal;
    /** The total hash depth for all hits. */
    Stats::Scalar<> hitDepthTotal;
    /** The number of hash misses. */
    Stats::Scalar<> hashMiss;
    /** The number of hash hits. */
    Stats::Scalar<> hashHit;
    /** @} */

  public:
    /**
     * Collection of parameters for the IIC.
     */
    class Params {
      public:
        /** The size in bytes of the cache. */
        int size;
        /** The number of sets in the primary table. */
        int numSets;
        /** The block size in bytes. */
        int blkSize;
        /** The associativity of the primary table. */
        int assoc;
        /** The number of cycles for each hash lookup. */
        int hashDelay;
        /** The number of cycles to read the data. */
        int hitLatency;
        /** The replacement policy. */
        Repl *rp;
        /** The subblock size in bytes. */
        int subblockSize;
    };

    /**
     * Construct and initialize this tag store.
     * @param params The IIC parameters.
     * @todo
     * Should make a way to have less tags in the primary than blks in the
     * cache. Also should be able to specify number of secondary blks.
     */
    IIC(Params &params);

    /**
     * Destructor.
     */
    virtual ~IIC();

    /**
     * Register the statistics.
     * @param name The name to prepend to the statistic descriptions.
     */
    void regStats(const std::string &name);

    /**
     * Regenerate the block address from the tag.
     * @param tag The tag of the block.
     * @param set Not needed for the iic.
     * @return The block address.
     */
    Addr regenerateBlkAddr(Addr tag, int set) {
        return (((Addr)tag << tagShift));
    }

    /**
     * Return the block size.
     * @return The block size.
     */
    int getBlockSize()
    {
        return blkSize;
    }

    /**
     * Return the subblock size.
     * @return The subblock size.
     */
    int getSubBlockSize()
    {
        return subSize;
    }

    /**
     * Return the hit latency.
     * @return the hit latency.
     */
    int getHitLatency() const
    {
        return hitLatency;
    }

    /**
     * Generate the tag from the address.
     * @param addr The address to a get a tag for.
     * @param blk Ignored here.
     * @return the tag.
     */
    Addr extractTag(Addr addr, IICTag *blk) const
    {
        return (addr >> tagShift);
    }

     /**
     * Generate the tag from the address.
     * @param addr The address to a get a tag for.
     * @return the tag.
     */
    Addr extractTag(Addr addr) const
    {
        return (addr >> tagShift);
    }

   /**
     * Return the set, always 0 for IIC.
     * @return 0.
     */
    int extractSet(Addr addr) const
    {
        return 0;
    }

    /**
     * Get the block offset of an address.
     * @param addr The address to get the offset of.
     * @return the block offset of the address.
     */
    int extractBlkOffset(Addr addr) const
    {
        return (addr & blkMask);
    }

    /**
     * Align an address to the block size.
     * @param addr the address to align.
     * @return The block address.
     */
    Addr blkAlign(Addr addr) const
    {
        return (addr & ~(Addr)blkMask);
    }

    /**
     * Check for the address in the tagstore.
     * @param asid The address space ID.
     * @param addr The address to find.
     * @return true if it is found.
     */
    bool probe(int asid, Addr addr) const;

    /**
     * Swap the position of two tags.
     * @param index1 The first tag location.
     * @param index2 The second tag location.
     */
    void tagSwap(unsigned long index1, unsigned long index2);

    /**
     * Clear the reference bit of the tag and return its old value.
     * @param index The pointer of the tag to manipulate.
     * @return The previous state of the reference bit.
     */
    bool clearRef(unsigned long index)
    {
        bool tmp = tagStore[index].isReferenced();
        tagStore[index].status &= ~BlkReferenced;
        return tmp;
    }

    /**
     * Decompress a block if it is compressed.
     * @param index The tag store index for the block to uncompress.
     */
    void decompressBlock(unsigned long index);

    /**
     * Try and compress a block if it is not already compressed.
     * @param index The tag store index for the block to compress.
     */
    void compressBlock(unsigned long index);

    /**
     * Invalidate the block containing the address.
     * @param asid The address space ID.
     * @param addr The address to invalidate.
     */
    void invalidateBlk(int asid, Addr addr);

    /**
     * Find the block and update the replacement data. This call also returns
     * the access latency as a side effect.
     * @param addr The address to find.
     * @param asid The address space ID.
     * @param lat The access latency.
     * @return A pointer to the block found, if any.
     */
    IICTag* findBlock(Addr addr, int asid, int &lat);

    /**
     * Find the block and update the replacement data. This call also returns
     * the access latency as a side effect.
     * @param pkt The req whose block to find
     * @param lat The access latency.
     * @return A pointer to the block found, if any.
     */
    IICTag* findBlock(Packet * &pkt, int &lat);

    /**
     * Find the block, do not update the replacement data.
     * @param addr The address to find.
     * @param asid The address space ID.
     * @return A pointer to the block found, if any.
     */
    IICTag* findBlock(Addr addr, int asid) const;

    /**
     * Find a replacement block for the address provided.
     * @param pkt The request to a find a replacement candidate for.
     * @param writebacks List for any writebacks to be performed.
     * @param compress_blocks List of blocks to compress, for adaptive comp.
     * @return The block to place the replacement in.
     */
    IICTag* findReplacement(Packet * &pkt, PacketList &writebacks,
                            BlkList &compress_blocks);

    /**
     * Read the data from the internal storage of the given cache block.
     * @param blk The block to read the data from.
     * @param data The buffer to read the data into.
     * @return The cache block's data.
     */
    void readData(IICTag *blk, uint8_t *data);

    /**
     * Write the data into the internal storage of the given cache block.
     * @param blk The block to write to.
     * @param data The data to write.
     * @param size The number of bytes to write.
     * @param writebacks A list for any writebacks to be performed. May be
     * needed when writing to a compressed block.
     */
    void writeData(IICTag *blk, uint8_t *data, int size,
                   PacketList & writebacks);

    /**
     * Perform a block aligned copy from the source address to the destination.
     * @param source The block-aligned source address.
     * @param dest The block-aligned destination address.
     * @param asid The address space DI.
     * @param writebacks List for any generated writeback pktuests.
     */
    void doCopy(Addr source, Addr dest, int asid, PacketList &writebacks);

    /**
     * If a block is currently marked copy on write, copy it before writing.
     * @param pkt The write request.
     * @param writebacks List for any generated writeback pktuests.
     */
    void fixCopy(Packet * &pkt, PacketList &writebacks);

    /**
     * Called at end of simulation to complete average block reference stats.
     */
    virtual void cleanupRefs();
private:
    /**
     * Return the hash of the address.
     * @param addr The address to hash.
     * @return the hash of the address.
     */
    unsigned hash(Addr addr) const;

    /**
     * Search for a block in the secondary tag store. Returns the number of
     * hash lookups as a side effect.
     * @param asid The address space ID.
     * @param tag The tag to match.
     * @param chain_ptr The first entry to search.
     * @param depth The number of hash lookups made while searching.
     * @return A pointer to the block if found.
     */
    IICTag *secondaryChain(int asid, Addr tag, unsigned long chain_ptr,
                            int *depth) const;

    /**
     * Free the resources associated with the next replacement block.
     * @param writebacks A list of any writebacks to perform.
     */
    void freeReplacementBlock(PacketList & writebacks);

    /**
     * Return the pointer to a free data block.
     * @param writebacks A list of any writebacks to perform.
     * @return A pointer to a free data block.
     */
    unsigned long getFreeDataBlock(PacketList & writebacks);

    /**
     * Get a free tag in the given hash set.
     * @param set The hash set to search.
     * @param writebacks A list of any writebacks to perform.
     * @return a pointer to a free tag.
     */
    IICTag* getFreeTag(int set, PacketList & writebacks);

    /**
     * Free the resources associated with the given tag.
     * @param tag_ptr The tag to free.
     */
    void freeTag(IICTag *tag_ptr);

    /**
     * Mark the given data block as being available.
     * @param data_ptr The data block to free.
     */
    void freeDataBlock(unsigned long data_ptr);
};
#endif // __IIC_HH__

