/* The copyright in this software is being made available under the BSD * License, included below. This software may be subject to other third party * and contributor rights, including patent rights, and no such rights are * granted under this license. * * Copyright (c) 2010 - 2019, ITU/ISO/IEC * 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 ITU/ISO/IEC 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 HOLDER 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. */ /** \file CacheModel.cpp \brief general cache class */ #include #include #include #include #define __STDC_FORMAT_MACROS #ifdef WIN32 #define strdup _strdup #endif #include "Utilities/program_options_lite.h" #include "CacheModel.h" #if JVET_J0090_MEMORY_BANDWITH_MEASURE #ifndef JVET_J0090_MEMORY_BANDWITH_MEASURE_PRINT_ACCESS_INFO #define JVET_J0090_MEMORY_BANDWITH_MEASURE_PRINT_ACCESS_INFO 0 #endif #ifndef JVET_J0090_MEMORY_BANDWITH_MEASURE_PRINT_FRAME #define JVET_J0090_MEMORY_BANDWITH_MEASURE_PRINT_FRAME -1 #endif enum CacheAddressMap { CACHE_MODE_1D = 0, CACHE_MODE_2D, MAX_NUM_CACHE_MODE }; namespace po = df::program_options_lite; CacheModel::CacheModel() { m_cacheEnable = false; m_cacheEnableFilter = false; m_cacheLineSize = 0; m_numCacheLine = 0; m_numWay = 0; m_cacheSize = 0; m_shift = 0; m_cacheAddr = nullptr; m_cachePoc = nullptr; m_cacheComp = nullptr; m_available = nullptr; m_refPoc = 0; m_base = nullptr; m_compID = MAX_NUM_COMPONENT; m_hitCount = nullptr; m_treeStatus = nullptr; m_missHitCount = 0; m_totalAccess = 0; m_hitCountSeq = 0; m_missHitCountSeq = 0; m_totalAccessSeq = 0; m_frameCount = 0; } CacheModel::~CacheModel() { } int CacheModel::xCalcPower( int num ) { int power = -1; for ( int i = 0 ; i < 32 ; i++ ) { if ( num == (1 << i) ) { power = i; break; } } if (power < 0) { THROW("non power of 2"); } return power; } void CacheModel::xConfigure(const std::string& filename ) { po::Options opts; opts.addOptions() ("CacheEnable", m_cacheEnable, false, "Cache Enable" ) ("CacheLineSize", m_cacheLineSize, 128, "Cache line size") ("NumCacheLine", m_numCacheLine, 32, "Number of cache line") ("NumWay", m_numWay, 4, "Number of way") ("CacheAddrMode", m_cacheAddrMode, 0, "Address mapping mode 0 : linear address 1 : 2D address") ("BlkWidth", m_cacheBlkWidth, 32, "Block width in 2D address mode") ("BlkHeight", m_cacheBlkHeight, 16, "Block height in 2D address mode") ("FrameReport", m_frameReport, false, "Report in each frame" ) ; po::setDefaults(opts); po::parseConfigFile( opts, filename ); if ( m_cacheLineSize > CACHE_MEM_ALIGN_SIZE ) { fprintf( stderr, "cache line size is bigger that memory alignment\n" ); fprintf( stderr, "This may lead mismatch among enviroments\n" ); } if ( m_cacheAddrMode == CACHE_MODE_2D ) { int blkSize = m_cacheBlkWidth * m_cacheBlkHeight; if (m_cacheLineSize % blkSize != 0 && blkSize % m_cacheLineSize) { THROW("CacheLineSize shall be multiple of BlkWidth x BlkHeight or BlkWidth x BlkHeight shall be multiple of CacheLineSize in 2D mode"); } } } // initilize cache information such as size void CacheModel::create(const std::string& cacheCfgFileName) { bool init = cacheCfgFileName == ""; if (cacheCfgFileName.length() > 1000) { THROW("config file name for cache model is too long. It shall be < 1000\n"); } if ( init ) { return; // no cache config } xConfigure(cacheCfgFileName); if ( !m_cacheEnable ) { return; } // set parameters m_cacheSize = m_numCacheLine * m_numWay; // calc address calculation parameter m_shift = xCalcPower( m_cacheLineSize ); // keep memory m_cacheAddr = new size_t [m_cacheSize]; m_cachePoc = new int [m_cacheSize]; m_cacheComp = new ComponentID [m_cacheSize]; m_available = new bool [m_cacheSize]; m_hitCount = new int [m_cacheSize]; // PLRU m_treeDepth = xCalcPower( m_numWay ); m_treeStatus = new int [m_numCacheLine]; if ( m_cacheLineSize > 0 && m_numCacheLine > 0 && m_numWay > 0 ) { m_cacheEnableFilter = true; } } // free memory void CacheModel::destroy() { if ( m_cacheAddr ) { delete [] m_cacheAddr; } if ( m_cachePoc ) { delete [] m_cachePoc; } if ( m_cacheComp ) { delete [] m_cacheComp; } if ( m_available ) { delete [] m_available; } if ( m_hitCount ) { delete [] m_hitCount; } if ( m_treeStatus ) { delete [] m_treeStatus; } } // clear cache status (set invalid for each entry) void CacheModel::clear() { if ( m_cacheEnable ) { ::memset( m_available, 0, m_cacheSize * sizeof(bool) ); ::memset( m_hitCount, 0, m_cacheSize * sizeof(int) ); m_missHitCount = 0; m_totalAccess = 0; } } // accuulate result for sequence level void CacheModel::accumulateFrame( ) { if ( m_cacheEnable ) { for ( int i = 0 ; i < m_cacheSize ; i++ ) { m_hitCountSeq += m_hitCount[ i ]; } m_missHitCountSeq += m_missHitCount; m_totalAccessSeq += m_totalAccess; if ( m_totalAccessSeq < 0 ) { fprintf( stdout, "detect overflow\n" ); } } } // report bandwidth, hit ratio and so on in a Frame void CacheModel::reportFrame( ) { if ( m_cacheEnable ) { if ( m_frameReport ) { int hitCount = 0; for ( int i = 0 ; i < m_cacheSize ; i++ ) { hitCount += m_hitCount[ i ]; } fprintf( stdout, "Cache Statics in frame %d\n", m_frameCount ); fprintf( stdout, "Hit ratio %5.2f [%%]\n", (100 * (double)(hitCount)) / m_totalAccess ); fprintf( stdout, "Required bandwidth %.1f [MB]\n", ((double)(m_missHitCount) * m_cacheLineSize) / (1024 * 1024) ); } m_frameCount++; } } void CacheModel::reportSequence( ) { if ( m_cacheEnable ) { fprintf( stdout, "Cache config\n" ); fprintf( stdout, "Cache line size: %d\n", m_cacheLineSize ); fprintf( stdout, "Cache line number %d\n", m_numCacheLine ); fprintf( stdout, "Cache way number %d\n\n", m_numWay ); fprintf( stdout, "Cache Statics in total\n" ); fprintf( stdout, "Hit ratio %5.2f [%%]\n", (100 * (double)(m_hitCountSeq)) / m_totalAccessSeq ); #ifdef _MSC_VER fprintf( stdout, "Hit count / total %I64d / %I64d\n", m_hitCountSeq, m_totalAccessSeq ); #else fprintf( stdout, "Hit count / total %" PRIi64 " / %" PRIi64 "\n", m_hitCountSeq, m_totalAccessSeq ); #endif fprintf( stdout, "Required bandwidth %.1f [MB] / frame\n", (((double)m_missHitCountSeq) * m_cacheLineSize) / (m_frameCount * 1024 * 1024) ); } } void CacheModel::setRefPicture( const Picture *refPic, const ComponentID CompID ) { m_refPoc = refPic->getPOC(); m_base = refPic->getOrigin(PIC_RECONSTRUCTION, CompID); m_compID = CompID; m_picWidth = refPic->getRecoBuf(CompID).stride; } bool CacheModel::xIsCacheHit( int pos, size_t addr ) { bool ret = false; if ( m_available[pos] ) { if ( addr == m_cacheAddr[pos] ) { if ( m_refPoc == m_cachePoc[pos] ) { if ( m_compID == m_cacheComp[pos] ) { ret = true; } } } } return ret; } //-- PLRU int CacheModel::xGetWayTreePLRU( int entry ) { int shift = 0; int way = 0; for ( int i = 0; i < m_treeDepth ; i++ ) { int flag = (m_treeStatus[ entry ] >> shift) & 0x1; shift = (shift << 1) + flag + 1; way = (way << 1) | (flag ^ 0x1); } xUpdateCacheStatus( entry, way ); return way; } void CacheModel::xUpdatePLRUStatus( int entry, int way ) { int val = m_treeStatus[ entry ]; int shift = 0; for ( int i = 0 ; i < m_treeDepth ; i++ ) { int flag = (way >> (m_treeDepth - i - 1)) & 0x1; val = (val & (~0 ^ (1 << shift))) | (flag << shift); // only set shift-th bit shift = (shift << 1) + 2 - flag; } m_treeStatus[ entry ] = val; } //-- other cache alg. (for future use) // get update way based on each update algorithm (Now Tree PLRU only) int CacheModel::xGetWay( int entry ) { // single way if ( m_numWay == 1 ) { return 0; } // multiway return xGetWayTreePLRU( entry ); } // update cache entry void CacheModel::xUpdateCache( int entry, size_t addr ) { int way = xGetWay( entry ); if (entry * m_numWay + way >= m_cacheSize || entry * m_numWay + way < 0) { THROW("incorrect cache info"); } m_cacheAddr[ entry * m_numWay + way ] = addr; m_cachePoc[ entry * m_numWay + way ] = m_refPoc; m_cacheComp[ entry * m_numWay + way ] = m_compID; m_available[ entry * m_numWay + way ] = true; } void CacheModel::xUpdateCacheStatus( int entry, int way ) { if ( m_numWay == 1 ) { return; } xUpdatePLRUStatus( entry, way ); } size_t CacheModel::xMapAddress( size_t offset ) { size_t ret; size_t xInPic, yInPic, blkPosX, blkPosY, xInBlk, yInBlk; switch (m_cacheAddrMode) { case CACHE_MODE_1D: // diret mapping return offset; case CACHE_MODE_2D: // 2D address mapping xInPic = offset % m_picWidth; yInPic = offset / m_picWidth; blkPosX = xInPic / m_cacheBlkWidth; blkPosY = yInPic / m_cacheBlkHeight; xInBlk = xInPic % m_cacheBlkWidth; yInBlk = yInPic % m_cacheBlkHeight; ret = m_picWidth * blkPosY * m_cacheBlkHeight; ret += blkPosX * m_cacheBlkWidth * m_cacheBlkHeight; ret += yInBlk * m_cacheBlkWidth; ret += xInBlk; return ret; default: THROW("Unknown address mode " << m_cacheAddrMode); return 0; } } // check cache hit/miss void CacheModel::cacheAccess( const Pel *addr, const std::string& fileName, const int lineNum ) { if ( !m_cacheEnable || !m_cacheEnableFilter ) { return; } bool hit = false; size_t cacheAddr = xMapAddress( (size_t) (addr - m_base) ) >> m_shift; int entry = (int) (cacheAddr % m_numCacheLine); int pos = entry * m_numWay; int way; // check cache hit in each way for ( way = 0 ; way < m_numWay ; way++ ) { if (xIsCacheHit(pos + way, cacheAddr)) { hit = true; break; } } #if JVET_J0090_MEMORY_BANDWITH_MEASURE_PRINT_ACCESS_INFO if ( m_frameCount == JVET_J0090_MEMORY_BANDWITH_MEASURE_PRINT_FRAME ) { fprintf( stdout, "%s %d:%p\n", fileName.c_str(), lineNum, addr ); } #endif if ( !hit ) { // read data from external memory m_missHitCount++; // update cache entry xUpdateCache( entry, cacheAddr ); } else { // update hit status m_hitCount[ pos + way ] ++; xUpdateCacheStatus( entry, way ); } m_totalAccess++; } void CacheModel::setCacheEnable( bool enable ) { m_cacheEnableFilter = enable; } #endif // JVET_J0090_MEMORY_BANDWITH_MEASURE