#include    "DCACHE.h"
#define	    bmsts_reset 0
#define	    bmsts_clear 1
#define	    bmsts_idle  2
#define	    bmsts_loadint  4
#define	    bmsts_loadext 5 //load from external main memory
#define		bmsts_loadlast 6
#define	    bmsts_write   7


#define	    bpsts_idle  0
void DCACHE::setinvalid()
{
	int r,s;
	for(r=0;r<dcacherows;r++) {
		for(s=0;s<dcachesets;s++) {
		    memtags[r][s]=-1;	//set a non-exist location
		    memlrus[r][s]=0;
		}
	}
}

bool	DCACHE::loaddata(const unsigned long *data,unsigned long startaddr,int dwords)
{
	int s,r,c;
	int i,j,a;
	unsigned long tag;
	bool overwrite=false;
	for(i=0;i<dwords;i++) {
	    a=startaddr+i*4;
	    c=(a&dcachewidthmask)>>2;
	    if ((i==0)||(c==0)) {
		r=((a>>dcachewidthb)&dcacherowmask);
		tag=(a>>dcachetagsh);
		s=tag&dcachesetmask;
		for(j=0;j<dcachesets;j++) {if (memtags[r][(s+j)&dcachesetmask]==-1) break;}
		if (j==dcachesets) {
		    print3("Warning:DCACHE loaddata full@A=%08X\n",a);
		    return(false);
		}
		s=(s+j)&dcachesetmask;
	    }
	    memtags[r][s]=tag;
	    memlrus[r][s]=dcachelrumask;
	    j=c&((CPUDATAWIDTH/32)-1);
	    c=c>>(CPUDATAWIDTHb-2);
	    *(unsigned long *)&memdatas[(((r<<dcachesetb)+s)<<dcachecolb)+c][j*4]=data[i];
	}
	noclear=true;
	return(true);
}

void DCACHE::entry()
{
	unsigned a;
	unsigned i,k;
	unsigned m;
	int bbaddr=0;
	int drow;
	int dset;
	int disnext;
	unsigned dtag;
	bool	drcheck;
	char bbmsts;

	B32		xmv;
	if (reset.read()) {
	    bmsts=bmsts_reset;
	    countWritesameblock=0;
	    countReadsameblock=0;
	    countWritehit=0;
	    countWritemis=0;
	    countReadhit=0;
	    countReadmis=0;
	    return;
	}
	drcheck=false;

	bool	dochecknewrow=((bmsts==bmsts_loadint)&&((bmcnt==1)||(bmcnt==dcachecols)));
	bool	dosetlru=((bmsts==bmsts_loadint)&&(bmcnt==1));
	if  (dochecknewrow) {
	    disnext=(bisnext||(dochecknewrow&&(bmcnt&dcachecols)));
	    drow=disnext?((brow+1)&dcacherowmask):brow;
	    dtag=(disnext&&(brow==dcacherowmask))?btag+1:btag;
	    for(i=0;i<dcachesets;i++) {if (memtags[drow][i]==dtag) break;}
	    if (i==dcachesets) {
		dset=0;
		for(i=1;i<dcachesets;i++) {
			if (memlrus[drow][i]<memlrus[drow][dset]) dset=i;
		}
		bchecktagfail=true;
	    } else {
		dset=i;
		bchecktagfail=false;
	    }
	    if (disnext) b1set=dset; else bset=dset;
	}
	if (dosetlru&&!bchecktag) {
	    if (bchecktagfail) {
		memtags[drow][dset]=dtag;
	    }
	    for(i=0;i<dcachesets;i++) {
	        if (i==dset) memlrus[drow][i]=dcachelrumask;
	        else if (memlrus[drow][i]!=0) memlrus[drow][i]--;
	    }
	} else if (!dosetlru) bchecktag=0;


	bbmsts=bmsts;
	switch(bmsts) {
	case bmsts_reset:
	    if (noclear) {
		baddr=-1;
		bmsts=bmsts_idle; 
		ram_ready.write(1);
	    } else {
		baddr=0;
		bmsts=bmsts_clear;
		ram_ready.write(0);
	    }
	    mwaiting=0;
	    mrwaiting=false;
	    mmodew=0;
	    maddr=0;
	    bmodew=0;
	    baddr=-1;
	    browtag=-1;
	    brow=0;
	    btag=0;
	    bdirty=0;
	    brvalid=0;
	    b1valid=0;
	    bisnext=false;
	    return;
	case bmsts_clear:
	    bbaddr=baddr>>CPUDATAWIDTHb;
	    for(i=0;i<dcachesets;i++) {
		memtags[bbaddr][i]=-1;
		memlrus[bbaddr][i]=0;
	    }
	    if (bbaddr==dcacherows-1) {
		bmsts=bmsts_idle;
		mwaiting=0;
		baddr=-1;
		ram_ready.write(1);
	    } else baddr=baddr+(CPUDATAWIDTH/8);
	    break;
	case bmsts_loadint: //load from cachedata to buff
		if (bmcnt==0) bmcnt++;
		else if (bmcnt==1) {
		    if (bchecktagfail&&!bchecktag) {
			if (bmodew) countWritemis++;
			else countReadmis++;
			xm_req.write(1);
			xm_seq.write(0);
			if (!bisnext&&(bdirty==((1<<dcachewidth)-1))) { //all one, no reload need
			    bmsts=bmsts_write;
			    bmcnt=0;
			} else {
			    bmsts=bmsts_loadext;
			    if (bisnext) {
				bmcnt=0;
				i=dcachewidth;
			    } else {
				for(i=0;i<dcachewidth/4;i++) {if (((bdirty>>(i*4))&0x0F)!=0x0F) break;}
				bmcnt=i;
				i=0;
			    }
			    xm_trans.write(1);
			    xm_write.write(0);
			    xm_addr.write((baddr&~dcachewidthmask)+bmcnt*4+i);
			}
		    } else {
			if (bmodew) countWritehit++;
			else countReadhit++;
			if ((dset!=0)&&!bchecktag) //one cycle for mem address=set1 latch
			    bmcnt=1;
			else if (bisnext) bmcnt=dcachecols+2;
			else bmcnt++;
		    }
		    bchecktag=1;
		} else 	if (bmcnt>=2) {
		    drcheck=!bmodew;
		    if (bmcnt==dcachecols+2) {//for next row, no we
			//if (bisnext) 
			    memcpy(&bdata[(bmcnt-2)*(CPUDATAWIDTH/8)],&memdatas[(((((brow+1)&dcacherowmask)<<dcachesetb)+b1set)<<dcachecolb)][0],CPUDATAWIDTH/8);
		    } else {
			k=(bdirty>>((bmcnt-2)*(CPUDATAWIDTH/8)))&((1<<CPUDATAWIDTH/8)-1);
			for(i=0;i<CPUDATAWIDTH/8;i++) {
			    if (!(k&1)) bdata[(bmcnt-2)*(CPUDATAWIDTH/8)+i]=memdatas[(((brow<<dcachesetb)+bset)<<dcachecolb)+bmcnt-2][i];
			    k>>=1;
			}
		    }
		    for(i=0;i<CPUDATAWIDTH/32;i++) brvalid|=(1<<((bmcnt-2)*(CPUDATAWIDTH/32)+i));

		    if (bmcnt==dcachecols+2) { //load next row, col0
			if (bmodew) bmsts=bmsts_write;
			else if (mrwaiting) bmsts=bmsts_loadlast;
			else bmsts=bmsts_idle;
			bmcnt=0;
			b1valid=true;
			bisnext=false;
		    } else if (bmcnt==dcachecols+1) {
			if (bchecktagfail) { //next row not exist
			    a=baddr&dcachewidthmask;
			    if (!bmodew&&(((a+msize-1)>>CPUDATAWIDTHb)>=dcachecols)) {
				bmcnt=1; //go lru set
				bisnext=true; 
			    } else {
				if (bmodew) bmsts=bmsts_write;
				else bmsts=bmsts_idle;
				bmcnt=0;
				b1valid=false;
				bisnext=false; 
			    }
			} else {
			    bmcnt++;
			}
		    } else if (bmcnt==dcachecols) {
			bisnext=true;
			bmcnt++;
		    } else {
			bmcnt++;
		    }
		}
		break;
	case bmsts_loadext: //load from pci to buff
		if (xm_grant.read()&&xm_ready.read()) {
		    if (xm_seq.read()) { //data in
			drcheck=true;
			xmv.d=xm_rdata.read();
			if (bisnext) {
			    if (bmcnt<=(CPUDATAWIDTH/32)) {
				*(unsigned *)&bdata[dcachewidth+(bmcnt-1)*4]=xmv.d;
			    }
			    brvalid|=(1<<((dcachewidth/4)+bmcnt-1));
			    i=((bmcnt-1)/(CPUDATAWIDTH/32));
			    k=((brow+1)&dcacherowmask);
			    *(unsigned *)&memdatas[(((k<<dcachesetb)+bset)<<dcachecolb)+i][0]=xmv.d;
			} else {
			    k=(bdirty>>((bmcnt-1)*4))&0x0F;
			    for(i=0;i<4;i++) {
				if (!(k&1)) bdata[(bmcnt-1)*4+i]=xmv.b[i];
				k>>=1;
			    }
			    brvalid|=(1<<(bmcnt-1));
			    i=((bmcnt-1)/(CPUDATAWIDTH/32));
			    memcpy(&memdatas[(((brow<<dcachesetb)+bset)<<dcachecolb)+i][0],&bdata[i*(CPUDATAWIDTH/8)],CPUDATAWIDTH/8);
			    //foe 64bit, low 32-bit is written twice
			}
		    }
		    if (bisnext) k=0;
		    else k=bdirty|((1<<(bmcnt*4))-1);
		    if ((!xm_trans.read())/*||(!bisnext&&(bdirty==((1<<dcachewidth)-1)))*/) {//last
			a=maddr&dcachewidthmask;
			if ((((a+msize-1)>>CPUDATAWIDTHb)>=dcachecols)&&(!bisnext)) {
			    bisnext=true;
			    bmsts=bmsts_loadint;
			} else {
			    if (bmodew) bmsts=bmsts_write;
				else if (mrwaiting) bmsts=bmsts_loadlast;
				else bmsts=bmsts_idle;
			}
			xm_trans.write(0);
			xm_seq.write(0);
			xm_last.write(0);
			bmcnt=0;
		    } else if ((bmcnt==((dcachewidth/4)-1))/*||(k==((1<<dcachewidth)-1))*/) {
			xm_req.write(0);
			xm_trans.write(0);
			xm_write.write(0);
			xm_seq.write(1);
			xm_last.write(1);
			bmcnt++;
		    } else {
			xm_trans.write(1);
			xm_write.write(0);
			xm_seq.write(1);
			i=bisnext?dcachewidth:0;
			xm_addr.write((baddr&~dcachewidthmask)+(bmcnt+1)*4+i);
			xm_last.write(bmcnt==((dcachewidth/4)-2));
			bmcnt++;
		    }
		} else if (!bisnext&&!xm_seq.read()) { //not start
		    if (bdirty==((1<<dcachewidth)-1)) { //all one, no reload need
			xm_trans.write(0);
			xm_seq.write(0);
			bmsts=bmsts_write;
			bmcnt=0;
			break;
		    } else {
			for(i=bmcnt;i<dcachewidth/4;i++) {if (((bdirty>>(i*4))&0x0F)!=0x0F) break;}
			bmcnt=i;
			i=bisnext?dcachewidth:0;
			xm_addr.write((baddr&~dcachewidthmask)+bmcnt*4+i);
		    }
		}
		break;
	case bmsts_loadlast:
		bmsts=bmsts_idle;
		break;
	case bmsts_write://!reload&&!reloadp, do write	
		if ((bmcnt==0)&&!xm_trans.read()) {
		    k=bdirty>>(dcachewidth-4);
		    if (mwaiting||(k!=0)||invalidbusy.read()) {
			xm_req.write(1);
			xm_trans.write(1);
			xm_write.write(1);
			xm_we.write(0x0F);
			for(i=0;i<dcachewidth/4;i++) {if (((bdirty>>(i*4))&0x0F)!=0) break;}
			bmcnt=i;
			xm_addr.write((baddr&~dcachewidthmask)+(bmcnt*4));
			xm_wdata.write(*(unsigned *)(bdata+(bmcnt*4)));
		    }
		} else if (xm_grant.read()&&xm_trans.read()&&xm_ready.read()) {
		    i=(bmcnt/(CPUDATAWIDTH/32));
		    memcpy(&memdatas[(((brow<<dcachesetb)+bset)<<dcachecolb)+i][0],&bdata[i*(CPUDATAWIDTH/8)],CPUDATAWIDTH/8);
		    k=bdirty>>((bmcnt+1)*4);
		    bdirty&=~(0x0F<<(bmcnt*4));
		    if ((bmcnt==(dcachewidth/4-1))||(k==0)) {//no furture data to write
			xm_req.write(0);
			xm_trans.write(0);
			bmsts=bmsts_idle;
			bmcnt=0;
		    } else {
			xm_trans.write(1);
			xm_write.write(1);
			xm_we.write(0x0F);
			xm_addr.write((baddr&~dcachewidthmask)+((bmcnt+1)*4));
			xm_wdata.write(*(unsigned *)&bdata[(bmcnt+1)*4]);
			bmcnt++;
		    }
		    xm_seq.write(1);
		}
		break;
	case bmsts_idle://
	    if (mwaiting) {
		mwaiting=0;
		a=maddr&dcachewidthmask;
		bmsts=bmsts_loadint;
		bmcnt=0;
		if (!mmodew&&(mrowtag==browtag)) {
		    countReadsameblock++;
		    a=maddr&dcachewidthmask;
		    if (msize<=4) {
			if ((maddr&3)==0) k=0x01;
			else k=0x03;
		    } else {
			if ((maddr&3)==0) k=0x03;
			else k=0x07;
		    }
		    if ((((brvalid>>(a>>2))&k)==k)) { //all valid
			for(i=0;i<(msize/4);i++) ram_rdata[i].write(*(unsigned *)&bdata[a+i*4]);
			bmsts=bmsts_idle;
			ram_ready.write(true);
		    } else {
			bisnext=true;
			mrwaiting=true;
			ram_ready.write(false);
		    }
		} else {
		    bisnext=false;
    		    bdirty=0;
		    brvalid=0;
		    bmcnt=0;
		    ram_ready.write(mmodew);
		    b1valid=false;
		    mrwaiting=!mmodew;
		}
		if (mmodew) { //write at different row
		    memcpy(bdata+a,mdata.b,msize);
		    m=(1<<msize)-1;
    		    m=m<<a;
		    bdirty|=m;
		    m=bdirty>>a;
		    for(i=0;i<CPUDATAWIDTH/32;i++) {
		        if (m&0x0F) brvalid|=(1<<((a>>2)+i));
		        m>>=4;
		    }
		}

		bmodew=mmodew;
		baddr=maddr;
		browtag=(baddr>>dcachewidthb);
		brow=browtag&dcacherowmask;
		btag=baddr>>dcachetagsh;
	    } else {
		if (invalidbusy.read()) bmsts=bmsts_reset;
	    }
	    break;
	}
	if (!binvalid.read()&&invalid.read()) {
	    invalidbusy.write(1);
	} else if ((bbmsts==bmsts_clear)&&(bbaddr==dcacherows-1)) {
	    invalidbusy.write(0);
	}
	binvalid.write(invalid.read());

	//////////////////////////////////////////////////////////
	if (ram_ready.read()) {
	    if (ram_trans.read()) {
		maddr=ram_addr.read();
		mrowtag=(maddr>>dcachewidthb);
		mrow=mrowtag&dcacherowmask;
		mtag=maddr>>dcachetagsh;
		msize=ram_size.read();
		msize=(1<<msize);
		if (ram_write.read()) {
		    for(i=0;i<CPUDATAWIDTH/32;i++) mdata.d[i]=ram_wdata[i].read();
		    mmodew=1;
		} else {
		    mmodew=0;
		}
		if (mmodew) {
		    if ((bmsts==bmsts_idle)||(((bmsts==bmsts_loadint)||(bmsts==bmsts_loadext)||(bmsts==bmsts_write))&&bmodew&&(mrowtag==browtag))) {
			if (mrowtag!=browtag) {
			    bdirty=0;
			    brvalid=0;
			} else countWritesameblock++;
			bmodew=true;
			baddr=maddr;
			a=maddr&dcachewidthmask;
			memcpy(bdata+a,mdata.b,msize);
			m=(1<<msize)-1;
			m=m<<a;
			bdirty|=m;
			m=bdirty>>a;
			for(i=0;i<CPUDATAWIDTH/32;i++) {
			    if (m&0x0F) brvalid|=(1<<((a>>2)+i));
			    m>>=4;
			}
			if ((bmsts==bmsts_idle)) {
			    bmsts=bmsts_loadint;
			    bmcnt=0;
			}
			ram_ready.write(1);
		    } else {
			mwaiting=1;
			ram_ready.write(0);
		    }
		} else {//!mmodew
		    if (mrowtag==browtag) {
			countReadsameblock++;
			a=maddr&dcachewidthmask;
			if (msize<=4) {
			    if ((maddr&3)==0) k=0x01;
			    else k=0x03;
			} else {
			    if ((maddr&3)==0) k=0x03;
			    else k=0x07;
			}
			if (((brvalid>>(a>>2))&k)==k) { //all valid
			    for(i=0;i<(msize/4);i++) ram_rdata[i].write(*(unsigned *)&bdata[a+i*4]);
			    ram_ready.write(1);
			} else {
			    mwaiting=true;
			    ram_ready.write(0);
			}
		    } else { //not same tag
			if ((bmsts==bmsts_idle)&&(!mwaiting)) {
			    bdirty=0;
			    brvalid=0;
			    bmodew=false;
			    baddr=maddr;
			    bmsts=bmsts_loadint;
			    bmcnt=0;
			    ram_ready.write(0);
			    mrwaiting=true;
			} else {
			    mwaiting=true;
			    ram_ready.write(0);
			}
		    }
		} //mmodew
		browtag=(baddr>>dcachewidthb);
		brow=browtag&dcacherowmask;
		btag=baddr>>dcachetagsh;
		b1valid=false;
	    } //ram_trans
	}//ram_ready
	if (mrwaiting&&drcheck) {
	    a=maddr&dcachewidthmask;
	    if (msize<=4) {
	    if ((maddr&3)==0) k=0x01;
		else k=0x03;
	    } else {
		if ((maddr&3)==0) k=0x03;
		else k=0x07;
	    }
	    if (((brvalid>>(a>>2))&k)==k) { //all valid
    		for(i=0;i<(msize/4);i++) ram_rdata[i].write(*(unsigned *)&bdata[a+i*4]);
		ram_ready.write(true);
		mrwaiting=false;
	    }
	}
}

void	DCACHE::statistic()
{
    	print2("DCACHE Write at same block=%d\n",countWritesameblock);
	print2("DCACHE Read at same block=%d\n",countReadsameblock);
	print2("DCACHE Write hit=%d\n",countWritehit);
	print2("DCACHE Read hit=%d\n",countReadhit);
	print2("DCACHE Write mis=%d\n",countWritemis);
	print2("DCACHE Read mis=%d\n",countReadmis);
}

